summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-08 04:37:18 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-08 04:37:18 +0000
commit560bd88fc47ae6d7bdfea05d8ffe9dfc6304a327 (patch)
treefbc94560cc9494a655df718c90255f01d59ade87
parentd2474ee3bda8f6437a9c223bbbb2beacb1c4ba43 (diff)
parentf6d2a2cbd8d083ef592ac0c46a89844bfb3ed488 (diff)
downloadplatform_testing-560bd88fc47ae6d7bdfea05d8ffe9dfc6304a327.tar.gz
Snap for 8554636 from f6d2a2cbd8d083ef592ac0c46a89844bfb3ed488 to sdk-releaseplatform-tools-33.0.2
Change-Id: I765af8e166a58b46dbf9ba294c5754174d2caef5
-rw-r--r--build/tasks/continuous_instrumentation_tests.mk2
-rw-r--r--libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java19
-rw-r--r--libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoTestMediaAppHelper.java (renamed from libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceExtensions.kt)17
-rw-r--r--libraries/app-helpers/interfaces/common/src/android/platform/helpers/IMapsHelper.java51
-rw-r--r--libraries/app-helpers/interfaces/common/src/android/platform/helpers/IYouTubeHelper.java14
-rw-r--r--libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IChromeHelper.java10
-rw-r--r--libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGmailHelper.java135
-rw-r--r--libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGoogleCameraHelper2.java29
-rw-r--r--libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java21
-rw-r--r--libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/MediaCenterHelperImpl.java48
-rw-r--r--libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/TestMediaAppHelperImpl.java87
-rw-r--r--libraries/automotive-helpers/notifications-app-helper/src/android/platform/helpers/AutoNotificationMockingHelperImpl.java5
-rw-r--r--libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoConfigConstants.java11
-rw-r--r--libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoMediaCenterConfigUtility.java49
-rw-r--r--libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoNotificationsConfigUtility.java8
-rw-r--r--libraries/collectors-helper/lyric/src/com/android/helpers/LyricMemProfilerHelper.java284
-rw-r--r--libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricMemProfilerHelperTest.java279
-rw-r--r--libraries/collectors-helper/statsd/src/com/android/helpers/CrashHelper.java12
-rw-r--r--libraries/collectors-helper/statsd/src/com/android/helpers/StatsdHelper.java19
-rw-r--r--libraries/collectors-helper/statsd/src/com/android/helpers/StatsdStatsHelper.java382
-rw-r--r--libraries/collectors-helper/statsd/src/com/android/helpers/UiInteractionFrameInfoHelper.java5
-rw-r--r--libraries/collectors-helper/statsd/test/src/com/android/helpers/StatsdStatsHelperTest.java573
-rw-r--r--libraries/collectors-helper/system/src/com/android/helpers/TimeInStateHelper.java199
-rw-r--r--libraries/collectors-helper/system/test/src/com/android/helpers/tests/TimeInStateHelperTest.java132
-rw-r--r--libraries/compatibility-common-util/OWNERS1
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/GasTest.java3
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/ITestResult.java9
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/ResultHandler.java4
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/ScreenshotsMetadataHandler.java88
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestResult.java12
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestScreenshotsMetadata.java165
-rw-r--r--libraries/compatibility-common-util/src/com/android/compatibility/common/util/VersionCodes.java2
-rw-r--r--libraries/device-collectors/src/main/java/android/device/collectors/AppVersionListener.java3
-rw-r--r--libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java32
-rw-r--r--libraries/device-collectors/src/main/java/android/device/collectors/LyricMemProfilerCollector.java49
-rw-r--r--libraries/device-collectors/src/main/java/android/device/collectors/ScreenRecordCollector.java44
-rw-r--r--libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/README.md4
-rw-r--r--libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/droidfood-run-level.pbbin0 -> 1200 bytes
-rw-r--r--libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/StatsdMetadataListener.java41
-rw-r--r--libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/TimeInStateListener.java68
-rw-r--r--libraries/device-collectors/src/test/java/android/device/collectors/BaseMetricListenerInstrumentedTest.java30
-rw-r--r--libraries/device-collectors/src/test/java/android/device/collectors/ScreenRecordCollectorTest.java70
-rw-r--r--libraries/device-collectors/src/test/platform/android/device/collectors/TimeInStateListenerTest.java79
-rw-r--r--libraries/flicker/.gitignore2
-rw-r--r--libraries/flicker/Android.bp28
-rw-r--r--libraries/flicker/README.md2
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/Flicker.kt8
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/FlickerBlockJUnit4ClassRunner.kt24
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/FlickerResult.kt28
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/FlickerRunResult.kt4
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt16
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/TransitionRunnerWithRules.kt26
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/annotation/Group2.kt1
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/annotation/Group3.kt1
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/annotation/Group4.kt24
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionData.kt3
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/assertions/Assertions.kt44
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionsChecker.kt42
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionError.kt44
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt107
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerSubject.kt23
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt27
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/helpers/SampleAppHelper.kt59
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt18
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt55
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt7
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt9
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt2
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt1
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt2
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt51
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRule.kt111
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRuleForTestSpec.kt96
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/AssertionEngine.kt113
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt76
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParser.kt123
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionData.kt77
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/BaseAssertion.kt114
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/Components.kt24
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/TransitionAssertor.kt116
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppComponentBaseTest.kt42
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtEnd.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtStart.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtEnd.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtStart.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerReplacesLauncher.kt42
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppWindowReplacesLauncherAsTopWindow.kt39
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/ComponentBaseTest.kt24
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAlways.kt41
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtEnd.kt40
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtStart.kt40
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesOutOfTop.kt24
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesToTop.kt24
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowReplacesAppAsTopWindow.kt38
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAlways.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtEnd.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtStart.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAlways.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtEnd.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtStart.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtEnd.kt43
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtStart.kt43
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowBecomesVisible.kt34
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsInvisibleAlways.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAlways.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtEnd.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtStart.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/RotationLayerAppearsAndVanishes.kt45
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtEnd.kt43
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtStart.kt43
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleLayersShownMoreThanOneConsecutiveEntry.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesOutOfTop.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesToTop.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/service/resources/config.json162
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerSubjectException.kt40
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerTraceSubject.kt62
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/RegionSubject.kt87
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/EventLogSubject.kt16
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/FocusEventSubject.kt8
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerSubject.kt56
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceEntrySubject.kt176
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayersTraceSubject.kt236
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerStateSubject.kt676
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerTraceSubject.kt446
-rw-r--r--libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowStateSubject.kt40
-rw-r--r--libraries/flicker/src/com/android/server/wm/proto/errors.proto35
-rw-r--r--libraries/flicker/src/com/android/server/wm/proto/tags.proto45
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Buffer.kt4
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Color.kt20
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt68
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt41
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Extensions.kt2
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/FlickerComponentName.kt125
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt3
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Point.kt16
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt30
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Region.kt16
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/Size.kt (renamed from libraries/flicker/src/com/android/server/wm/traces/common/Bounds.kt)11
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt139
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt293
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt35
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt33
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt42
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt67
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt175
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt53
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt13
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt45
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt50
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/ITagProcessor.kt34
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/ITransitionAssertor.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt72
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/TaggingEngine.kt81
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppCloseProcessor.kt134
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppLaunchProcessor.kt82
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/BaseFsmState.kt59
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/FSMState.kt77
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeAppearProcessor.kt113
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeDisappearProcessor.kt123
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipEnterProcessor.kt154
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExitProcessor.kt134
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExpandProcessor.kt113
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipResizeProcessor.kt85
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/RotationProcessor.kt165
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/service/processors/TransitionProcessor.kt112
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/tags/Tag.kt28
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/tags/TagState.kt37
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/tags/TagTrace.kt47
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/tags/Transition.kt32
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/tags/TransitionTag.kt32
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt362
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt43
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt27
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt28
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt18
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt19
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt69
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Task.kt (renamed from libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ActivityTask.kt)80
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt64
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt22
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt34
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt74
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt76
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt59
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt16
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/Condition.kt219
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/DeviceDumpParser.kt (renamed from libraries/flicker/src/com/android/server/wm/traces/parser/DeviceStateDump.kt)30
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt42
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/errors/ErrorTraceParserUtil.kt103
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/errors/Extensions.kt70
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt65
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt7
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/tags/Extensions.kt67
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/tags/TagTraceParserUtil.kt104
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt10
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt461
-rw-r--r--libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt138
-rw-r--r--libraries/flicker/test/Android.bp3
-rw-r--r--libraries/flicker/test/AndroidManifest.xml4
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsSurfaceFlingerTrace.winscopebin0 -> 3781235 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsTagTrace.winscopebin0 -> 155 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsWindowManagerTrace.winscopebin0 -> 1859788 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerInvalidTrace.winscopebin0 -> 320631 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerTrace.winscopebin0 -> 1778174 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerInvalidTrace.winscopebin0 -> 1028027 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerTrace.winscopebin0 -> 732077 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/assertionsConfig.json120
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/config.json127
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerInvalidTrace.winscopebin0 -> 1476064 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerTrace.winscopebin0 -> 1214300 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/assertors/rotation/WindowManagerTrace.winscopebin0 -> 913955 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/layers_dump_with_display.pbbin0 -> 20642 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/layers_trace_close_app_with_rotation.winscopebin0 -> 2816305 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/layers_trace_occluded.pbbin0 -> 841727 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/layers_trace_openchrome.pbbin0 -> 1473269 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/layers_trace_pip_wmshell.pbbin0 -> 926194 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/SurfaceFlingerTrace.winscopebin0 -> 2128704 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/WindowManagerTrace.winscopebin0 -> 1514310 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/SurfaceFlingerTrace.winscopebin0 -> 3841317 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/WindowManagerTrace.winscopebin0 -> 1413681 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/SurfaceFlingerTrace.winscopebin0 -> 2879688 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/WindowManagerTrace.winscopebin0 -> 1481707 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/SurfaceFlingerTrace.winscopebin0 -> 5236233 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/WindowManagerTrace.winscopebin0 -> 1484506 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/SurfaceFlingerTrace.winscopebin0 -> 1499573 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/WindowManagerTrace.winscopebin0 -> 500456 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/SurfaceFlingerTrace.winscopebin0 -> 3486411 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/WindowManagerTrace.winscopebin0 -> 979388 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/SurfaceFlingerTrace.winscopebin0 -> 1749215 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/WindowManagerTrace.winscopebin0 -> 661571 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/SurfaceFlingerTrace.winscopebin0 -> 4342265 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/WindowManagerTrace.winscopebin0 -> 1943247 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/SurfaceFlingerTrace.winscopebin0 -> 3564534 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/WindowManagerTrace.winscopebin0 -> 1225480 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/SurfaceFlingerTrace.winscopebin0 -> 1229224 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/WindowManagerTrace.winscopebin0 -> 557466 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/SurfaceFlingerTrace.winscopebin0 -> 2974910 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/WindowManagerTrace.winscopebin0 -> 1360556 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/SurfaceFlingerTrace.winscopebin0 -> 311803 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/WindowManagerTrace.winscopebin0 -> 108527 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/SurfaceFlingerTrace.winscopebin0 -> 642743 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/WindowManagerTrace.winscopebin0 -> 215930 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/SurfaceFlingerTrace.winscopebin0 -> 1339437 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/WindowManagerTrace.winscopebin0 -> 379473 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/SurfaceFlingerTrace.winscopebin0 -> 3047960 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/WindowManagerTrace.winscopebin0 -> 645989 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/SurfaceFlingerTrace.winscopebin0 -> 3953617 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/WindowManagerTrace.winscopebin0 -> 1349713 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/SurfaceFlingerTrace.winscopebin0 -> 3124394 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/WindowManagerTrace.winscopebin0 -> 1019396 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/SurfaceFlingerTrace.winscopebin0 -> 4193487 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/WindowManagerTrace.winscopebin0 -> 1391924 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/SurfaceFlingerTrace.winscopebin0 -> 1863084 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/WindowManagerTrace.winscopebin0 -> 952612 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/SurfaceFlingerTrace.winscopebin0 -> 126416 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/WindowManagerTrace.winscopebin0 -> 11945 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/SurfaceFlingerTrace.winscopebin0 -> 5239082 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/WindowManagerTrace.winscopebin0 -> 2466122 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/SurfaceFlingerTrace.winscopebin0 -> 755398 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/WindowManagerTrace.winscopebin0 -> 349586 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/SurfaceFlingerTrace.winscopebin0 -> 5209824 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/WindowManagerTrace.winscopebin0 -> 1321186 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/SurfaceFlingerTrace.winscopebin0 -> 4565752 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/WindowManagerTrace.winscopebin0 -> 1240937 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/SurfaceFlingerTrace.winscopebin0 -> 4565752 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/WindowManagerTrace.winscopebin0 -> 1240937 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/SurfaceFlingerTrace.winscopebin0 -> 5217832 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/WindowManagerTrace.winscopebin0 -> 1324312 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/SurfaceFlingerTrace.winscopebin0 -> 2648358 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/WindowManagerTrace.winscopebin0 -> 311956 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/SurfaceFlingerTrace.winscopebin0 -> 2915256 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/WindowManagerTrace.winscopebin0 -> 1649742 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/SurfaceFlingerTrace.winscopebin0 -> 3345021 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/WindowManagerTrace.winscopebin0 -> 1368237 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/SurfaceFlingerTrace.winscopebin0 -> 458206 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/WindowManagerTrace.winscopebin0 -> 314807 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/SurfaceFlingerTrace.winscopebin0 -> 1307222 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/WindowManagerTrace.winscopebin0 -> 819089 bytes
-rwxr-xr-xlibraries/flicker/test/assets/testdata/wm_trace_launcher_visible_background.pbbin0 -> 315511 bytes
-rwxr-xr-xlibraries/flicker/test/assets/testdata/wm_trace_open_from_overview.pbbin0 -> 491839 bytes
-rw-r--r--libraries/flicker/test/assets/testdata/wm_trace_split_screen.pbbin0 -> 352491 bytes
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.kt52
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/CommonConstants.kt43
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt14
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/FlickerDSLTest.kt27
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceTest.kt64
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/UiDeviceExtensionsTest.kt30
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt13
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateSubjectTest.kt190
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceSubjectTest.kt137
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerSubjectTest.kt (renamed from libraries/flicker/test/src/com/android/server/wm/flicker/LayerSubjectTest.kt)43
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntrySubjectTest.kt (renamed from libraries/flicker/test/src/com/android/server/wm/flicker/LayerTraceEntrySubjectTest.kt)50
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceEntryTest.kt (renamed from libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceEntryTest.kt)21
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceSubjectTest.kt (renamed from libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.kt)131
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt149
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt2
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt15
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/AssertionEngineTest.kt88
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/ErrorParserTest.kt64
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/FassMockTest.kt57
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/RotationMockTest.kt62
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/TagParserTest.kt66
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/TraceIsTaggableTest.kt69
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleForTestSpecTest.kt68
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleTest.kt33
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AppLaunchAssertionsTest.kt71
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParserTest.kt43
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/RotationAssertionsTest.kt74
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppCloseProcessorTest.kt158
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppLaunchProcessorTest.kt161
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeAppearProcessorTest.kt118
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeDisappearProcessorTest.kt124
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipEnterProcessorTest.kt142
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExitProcessorTest.kt96
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExpandProcessorTest.kt74
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipResizeProcessorTest.kt78
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/RotationProcessorTest.kt212
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateHelperTest.kt (renamed from libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateHelperTest.kt)156
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt323
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt206
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceTest.kt (renamed from libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.kt)25
-rw-r--r--libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt65
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/ClassMetricRule.java67
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/CoolDownRule.java3
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/FailureWatcher.java112
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/LandscapeOrientationRule.java18
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/MapsPipRule.java56
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/PhotoUploadRule.java94
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/ScreenRecordRule.java81
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/TestMetricRule.java45
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/YouTubePipRule.java57
-rw-r--r--libraries/health/rules/src/android/platform/test/rule/flicker/WindowManagerFlickerRuleCommon.java14
-rw-r--r--libraries/health/rules/tests/src/android/platform/test/rule/ClassMetricRuleTest.java141
-rw-r--r--libraries/health/rules/tests/src/android/platform/test/rule/CoolDownRuleTest.java10
-rw-r--r--libraries/health/rules/tests/src/android/platform/test/rule/TestMetricRuleTest.java49
-rw-r--r--libraries/health/utils/src/android/platform/test/util/TestFilter.java68
-rw-r--r--libraries/health/utils/tests/src/android/platform/test/util/TestFilterTest.java79
-rw-r--r--libraries/screenshot/Android.bp69
-rw-r--r--libraries/screenshot/AndroidManifest.xml33
-rw-r--r--libraries/screenshot/OWNERS5
-rw-r--r--libraries/screenshot/TEST_MAPPING12
-rw-r--r--libraries/screenshot/proto/src/main/proto/screenshot_result.proto109
-rw-r--r--libraries/screenshot/src/androidTest/assets/checkbox_checked.pngbin0 -> 586 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox.pngbin0 -> 6307 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox_round.pngbin0 -> 6427 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray.pngbin0 -> 6240 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_dark.pngbin0 -> 6736 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_moved_1px.pngbin0 -> 6773 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/round_rect_gray.pngbin0 -> 426 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/round_rect_gray_dark.pngbin0 -> 3378 bytes
-rw-r--r--libraries/screenshot/src/androidTest/assets/round_rect_green.pngbin0 -> 4252 bytes
-rw-r--r--libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt75
-rw-r--r--libraries/screenshot/src/androidTest/java/platform/test/screenshot/MSSIMMatcherTest.kt135
-rw-r--r--libraries/screenshot/src/androidTest/java/platform/test/screenshot/PixelPerfectMatcherTest.kt58
-rw-r--r--libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt179
-rw-r--r--libraries/screenshot/src/androidTest/java/platform/test/screenshot/utils/BitmapUtils.kt28
-rw-r--r--libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt212
-rw-r--r--libraries/screenshot/src/main/java/platform/test/screenshot/PlatformScreenshotTestRule.kt39
-rw-r--r--libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt391
-rw-r--r--libraries/screenshot/src/main/java/platform/test/screenshot/matchers/BitmapMatcher.kt52
-rw-r--r--libraries/screenshot/src/main/java/platform/test/screenshot/matchers/MSSIMMatcher.kt274
-rw-r--r--libraries/screenshot/src/main/java/platform/test/screenshot/matchers/PixelPerfectMatcher.kt80
-rw-r--r--tests/automotive/functional/home/Android.bp2
-rw-r--r--tests/automotive/functional/mediacenter/src/android/platform/tests/MediaTestAppTest.java90
-rw-r--r--tests/automotive/functional/mediacenter/src/android/platform/tests/NoUserLoggedInTest.java67
-rw-r--r--tests/automotive/functional/notifications/Android.bp2
-rw-r--r--tests/automotive/functional/settings/Android.bp2
-rw-r--r--tests/automotive/health/settings/src/android/platform/scenario/settings/ScrollInApp.java4
-rw-r--r--tests/bootdoa/Android.mk1
-rw-r--r--tests/codecoverage/native/cpp/Android.bp18
-rw-r--r--tests/codecoverage/native/cpp/coverage_cpp_smoke_test.cpp29
-rw-r--r--tests/codecoverage/native/rust/Android.bp19
-rw-r--r--tests/codecoverage/native/rust/coverage_rust_smoke_test.rs27
-rw-r--r--tests/functional/devicehealthchecks/assets/bug_map6
-rw-r--r--tests/functional/devicehealthchecks/src/com/android/devicehealthchecks/CrashCheckBase.java1
-rw-r--r--tests/health/scenarios/src/android/platform/test/scenario/annotation/LargeScreenOnly.java31
380 files changed, 17353 insertions, 2836 deletions
diff --git a/build/tasks/continuous_instrumentation_tests.mk b/build/tasks/continuous_instrumentation_tests.mk
index 77121598a..1674d08cc 100644
--- a/build/tasks/continuous_instrumentation_tests.mk
+++ b/build/tasks/continuous_instrumentation_tests.mk
@@ -81,6 +81,8 @@ continuous_instrumentation_tests_api_coverage : $(coverage_report)
$(call dist-for-goals, continuous_instrumentation_tests_api_coverage, \
$(coverage_report):$(name)-api_coverage.html)
+ALL_TARGETS.$(coverage_report).META_LIC:=$(module_license_metadata)
+
# Also build this when you run "make tests".
# This allow us to not change the build server config.
tests : continuous_instrumentation_tests_api_coverage
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java
index 530fb46d9..49174abac 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java
@@ -151,4 +151,23 @@ public interface IAutoMediaHelper extends IAppHelper {
* @return true if all app names in mediaAppsNames shows up in Media Apps Grid
*/
boolean areMediaAppsPresent(List<String> mediaAppsNames);
+
+ /**
+ * Setup expectations: "Media apps" Grid is open.
+ *
+ * @param appName App name to open
+ */
+ void openApp(String appName);
+
+ /**
+ * Setup expectations: Media app is open.
+ */
+ void openMediaAppSettingsPage();
+
+ /**
+ * Setup expectations: Media app is open. Account not logged in.
+ *
+ * @return Error message for no user login
+ */
+ String getMediaAppUserNotLoggedInErrorMessage();
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceExtensions.kt b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoTestMediaAppHelper.java
index d6a6810ea..4ed751eba 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceExtensions.kt
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoTestMediaAppHelper.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.traces.layers
+package android.platform.helpers;
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
-import com.android.server.wm.traces.parser.toAndroidRegion
-
-fun LayerTraceEntry.getVisibleBounds(layerName: String): android.graphics.Region {
- return flattenedLayers.firstOrNull { it.name.contains(layerName) && it.isVisible }
- ?.visibleRegion?.toAndroidRegion()
- ?: android.graphics.Region()
-} \ No newline at end of file
+public interface IAutoTestMediaAppHelper extends IAppHelper {
+ /**
+ * Loads Media files in Test Media app
+ * Setup expectations: Test Media app Settings page is open.
+ */
+ void loadMediaInLocalMediaTestApp();
+}
diff --git a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IMapsHelper.java b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IMapsHelper.java
index 894a861b7..47b7f4684 100644
--- a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IMapsHelper.java
+++ b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IMapsHelper.java
@@ -24,29 +24,29 @@ public interface IMapsHelper extends IAppHelper {
/**
* Setup expectation: On the standard Map screen in any setup.
*
- * Best effort attempt to go to the query screen (if not currently there),
- * does a search, and selects the results.
+ * <p>Best effort attempt to go to the query screen (if not currently there), does a search, and
+ * selects the results.
*/
public void doSearch(String query);
/**
* Setup expectation: Destination is selected.
*
- * Best effort attempt to go to the directions screen for the selected destination.
+ * <p>Best effort attempt to go to the directions screen for the selected destination.
*/
public void getDirections();
/**
* Setup expectation: On directions screen.
*
- * Best effort attempt to start navigation for the selected destination.
+ * <p>Best effort attempt to start navigation for the selected destination.
*/
public void startNavigation();
/**
* Setup expectation: On navigation screen.
*
- * Best effort attempt to stop navigation, and go back to the directions screen.
+ * <p>Best effort attempt to stop navigation, and go back to the directions screen.
*/
public void stopNavigation();
@@ -107,8 +107,7 @@ public interface IMapsHelper extends IAppHelper {
/**
* Setup expectation: On the standard Map screen in any setup.
*
- * <p>Best effort attempt to go to the query screen (if not currently there),
- * does a search.
+ * <p>Best effort attempt to go to the query screen (if not currently there), does a search.
*/
public default void inputSearch(String query) {
throw new UnsupportedOperationException("Not yet implemented.");
@@ -177,4 +176,42 @@ public interface IMapsHelper extends IAppHelper {
public default void clickBaseCompassButton() {
throw new UnsupportedOperationException("Not yet implemented.");
}
+
+ /**
+ * Setup expectation: On the home screen for foldable device.
+ *
+ * <p>This method checks that the home page has side panel view.
+ */
+ public default boolean isSidePanelOpened() {
+ throw new UnsupportedOperationException("Not implemented yet.");
+ }
+
+ /**
+ * Setup expectation: On the home screen for foldable device.
+ *
+ * <p>Click the button to close the side panel when it is opened.
+ */
+ public default void closeSidePanel() {
+ throw new UnsupportedOperationException("Not implemented yet.");
+ }
+
+ /**
+ * Setup expectation: On the home screen for foldable device.
+ *
+ * <p>Get the UiObject2 of main map container.
+ */
+ public default UiObject2 getMainMapContainer() {
+ throw new UnsupportedOperationException("Not implemented yet.");
+ }
+
+ /**
+ * Setup expectation: On the home screen.
+ *
+ * <p>This method checks if Maps is on the main page.
+ *
+ * @param sidePanelOpened Whether the side panel view should be opened or not.
+ */
+ public default boolean isOnMapsMainPage(boolean sidePanelOpened) {
+ throw new UnsupportedOperationException("Not implemented yet.");
+ }
}
diff --git a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IYouTubeHelper.java b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IYouTubeHelper.java
index fb84f631b..790da94e0 100644
--- a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IYouTubeHelper.java
+++ b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/IYouTubeHelper.java
@@ -222,4 +222,18 @@ public interface IYouTubeHelper extends IAppHelper {
* <p>It presses YouTube PiP view twice to return to the main app.
*/
public void backFromYouTubeFromPip();
+
+ /**
+ * Setup expectation: YouTube is on the library page.
+ *
+ * <p>presses Your videos tab.
+ */
+ public void goToYourVideos();
+
+ /**
+ * Setup expectation: YouTube is on the Your videos page.
+ *
+ * <p>presses the video name to play.
+ */
+ public void playYourVideo(String videoName);
}
diff --git a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IChromeHelper.java b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IChromeHelper.java
index d61727656..1ff75eae0 100644
--- a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IChromeHelper.java
+++ b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IChromeHelper.java
@@ -17,6 +17,7 @@
package android.platform.helpers;
import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
public interface IChromeHelper extends IAppHelper {
public enum MenuItem {
@@ -253,4 +254,13 @@ public interface IChromeHelper extends IAppHelper {
public default void scrollPage(Direction dir, float percent) {
throw new UnsupportedOperationException("Not yet implemented.");
}
+
+ /**
+ * Setup expectations: Chrome is open on a page.
+ *
+ * <p>Get the UiObject2 of the page screen.
+ */
+ public default UiObject2 getWebPage() {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
}
diff --git a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGmailHelper.java b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGmailHelper.java
index 551c4abc8..4318b3b94 100644
--- a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGmailHelper.java
+++ b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGmailHelper.java
@@ -17,6 +17,8 @@
package android.platform.helpers;
import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
import java.util.List;
public interface IGmailHelper extends IAppHelper {
@@ -24,19 +26,17 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and the navigation bar is visible.
*
- * This method will navigate to the Inbox or Primary, depending on the name.
+ * <p>This method will navigate to the Inbox or Primary, depending on the name.
*/
public void goToInbox();
- /**
- * Alias method for AbstractGmailHelper#goToInbox
- */
+ /** Alias method for AbstractGmailHelper#goToInbox */
public void goToPrimary();
/**
* Setup expectations: Gmail is open on the Inbox or Primary page.
*
- * This method will open a new e-mail to compose and block until complete.
+ * <p>This method will open a new e-mail to compose and block until complete.
*/
public void goToComposeEmail();
@@ -57,16 +57,16 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and on the Inbox or Primary page.
*
- * This method will open the (index)'th visible e-mail in the list and block until the e-mail is
- * visible in the foreground. The top-most visible e-mail will always be labeled 0. To get the
- * number of visible e-mails, consult the getVisibleEmailCount() function.
+ * <p>This method will open the (index)'th visible e-mail in the list and block until the e-mail
+ * is visible in the foreground. The top-most visible e-mail will always be labeled 0. To get
+ * the number of visible e-mails, consult the getVisibleEmailCount() function.
*/
public void openEmailByIndex(int index);
/**
* Setup expectations: Gmail is open and on the Inbox or Primary page.
*
- * This method will return the number of visible e-mails for use with the #openEmailByIndex
+ * <p>This method will return the number of visible e-mails for use with the #openEmailByIndex
* method.
*/
public int getVisibleEmailCount();
@@ -74,58 +74,58 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and an e-mail is open in the foreground.
*
- * This method will press reply, send a reply e-mail with the given parameters, and block until
- * the original message is in the foreground again.
+ * <p>This method will press reply, send a reply e-mail with the given parameters, and block
+ * until the original message is in the foreground again.
*/
public void sendReplyEmail(String address, String body);
/**
* Setup expectations: Gmail is open and composing an e-mail.
*
- * This method will set the e-mail's To address and block until complete.
+ * <p>This method will set the e-mail's To address and block until complete.
*/
public void setEmailToAddress(String address);
/**
* Setup expectations: Gmail is open and composing an e-mail.
*
- * This method will set the e-mail's subject and block until complete.
+ * <p>This method will set the e-mail's subject and block until complete.
*/
public void setEmailSubject(String subject);
/**
* Setup expectations: Gmail is open and composing an e-mail.
*
- * This method will set the e-mail's Body (doesn't use keyboard) and block until complete. Focus
- * will remain on the e-mail body after completion.
+ * <p>This method will set the e-mail's Body (doesn't use keyboard) and block until complete.
+ * Focus will remain on the e-mail body after completion.
*
- * * @param body The messages to input in the e-mail body.
+ * <p>* @param body The messages to input in the e-mail body.
*/
public void setEmailBody(String body);
/**
* Setup expectations: Gmail is open and composing an e-mail.
*
- * This method inputs the e-mail body.
+ * <p>This method inputs the e-mail body.
*
* @param body The messages to input in the e-mail body.
* @param useKeyboard Types out the e-mail body by keyboard or not.
*/
- default public void setEmailBody(String body, boolean useKeyboard) {
+ public default void setEmailBody(String body, boolean useKeyboard) {
throw new UnsupportedOperationException("Not yet implemented.");
}
/**
* Setup expectations: Gmail is open and composing an e-mail.
*
- * This method will press send and block until the device is idle on the original e-mail.
+ * <p>This method will press send and block until the device is idle on the original e-mail.
*/
public void clickSendButton();
/**
* Setup expectations: Gmail is open and composing an e-mail.
*
- * This method will get the e-mail's composition's body and block until complete.
+ * <p>This method will get the e-mail's composition's body and block until complete.
*
* @return {String} the text contained in the email composition's body.
*/
@@ -134,58 +134,60 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and the navigation drawer is visible.
*
- * This method will open the navigation drawer and block until complete.
+ * <p>This method will open the navigation drawer and block until complete.
*/
public void openNavigationDrawer();
/**
* Setup expectations: Gmail is open and the navigation drawer is open.
*
- * This method will close the navigation drawer and returns true otherwise false
+ * <p>This method will close the navigation drawer and returns true otherwise false
*/
public boolean closeNavigationDrawer();
/**
* Setup expectations: Gmail is open and the navigation drawer is open.
*
- * This method will scroll the navigation drawer and block until idle. Only accepts UP and DOWN.
+ * <p>This method will scroll the navigation drawer and block until idle. Only accepts UP and
+ * DOWN.
*/
public void scrollNavigationDrawer(Direction dir);
/**
* Setup expectations: Gmail is open and the navigation drawer is open.
*
- * This method will fling the navigation drawer and block until idle. Only accepts UP and DOWN.
+ * <p>This method will fling the navigation drawer and block until idle. Only accepts UP and
+ * DOWN.
*/
public void flingNavigationDrawer(Direction dir);
/**
* Setup expectations: Gmail is open and a mailbox is open.
*
- * This method will scroll the mailbox view.
+ * <p>This method will scroll the mailbox view.
*
- * @param direction The direction to scroll, only accepts UP and DOWN.
- * @param amount The amount to scroll
- * @param scrollToEnd Whether or not to scroll to the end
+ * @param direction The direction to scroll, only accepts UP and DOWN.
+ * @param amount The amount to scroll
+ * @param scrollToEnd Whether or not to scroll to the end
*/
public void scrollMailbox(Direction direction, float amount, boolean scrollToEnd);
/**
* Setup expectations: Gmail is open and an email is open.
*
- * This method will scroll the current email.
+ * <p>This method will scroll the current email.
*
- * @param direction The direction to scroll, only accepts UP and DOWN.
- * @param amount The amount to scroll
- * @param scrollToEnd Whether or not to scroll to the end
+ * @param direction The direction to scroll, only accepts UP and DOWN.
+ * @param amount The amount to scroll
+ * @param scrollToEnd Whether or not to scroll to the end
*/
public void scrollEmail(Direction direction, float amount, boolean scrollToEnd);
/**
* Setup expectations: Gmail is open and the navigation drawer is open.
*
- * This method will open the mailbox with the given name and block until emails in
- * that mailbox have loaded.
+ * <p>This method will open the mailbox with the given name and block until emails in that
+ * mailbox have loaded.
*
* @param mailboxName The case insensitive name of the mailbox to open
*/
@@ -194,16 +196,16 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and an email is open.
*
- * This method will return to the mailbox the current email was opened from.
+ * <p>This method will return to the mailbox the current email was opened from.
*/
public void returnToMailbox();
/**
* Setup expectations: Gmail is open and an email is open.
*
- * This method starts downloading the attachment at the specified index in the current email.
- * The download happens in the background. This method returns immediately after starting
- * the download and does not wait for the download to complete.
+ * <p>This method starts downloading the attachment at the specified index in the current email.
+ * The download happens in the background. This method returns immediately after starting the
+ * download and does not wait for the download to complete.
*
* @param index The index of the attachment to download
*/
@@ -212,7 +214,7 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and an email is open.
*
- * This method gets every link target in an open email by traversing the UI tree of the body
+ * <p>This method gets every link target in an open email by traversing the UI tree of the body
* of the open message.
*
* @return an iterator over the links in the message
@@ -222,7 +224,7 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and an email is open.
*
- * This method clicks the link in the open email with the given target.
+ * <p>This method clicks the link in the open email with the given target.
*
* @param target the target of the link to click
*/
@@ -245,11 +247,11 @@ public interface IGmailHelper extends IAppHelper {
/**
* Setup expectations: Gmail is open and an email is open.
*
- * This method swipes the current email.
+ * <p>This method swipes the current email.
*
* @param direction The direction to swipe, only accepts LEFT and RIGHT.
*/
- default public void swipeEmail(Direction direction) {
+ public default void swipeEmail(Direction direction) {
throw new UnsupportedOperationException("Not yet implemented.");
}
@@ -261,4 +263,53 @@ public interface IGmailHelper extends IAppHelper {
public default void openAccountMenu() {
throw new UnsupportedOperationException("Not yet implemented.");
}
+
+ /**
+ * Setup expectations: Gmail mailbox is open.
+ *
+ * <p>The UiObject2 for Gmail to get mail contents container view.
+ */
+ public UiObject2 getGmailContentsContainer();
+
+ /**
+ * Setup expectations: Gmail primary mail is open.
+ *
+ * <p>This method will check if the current view is the email contents page.
+ */
+ public boolean isOnGmailContentsPage();
+
+ /**
+ * Setup expectation: Gmail is open.
+ *
+ * <p>Get the UiObject2 of mail button icon.
+ */
+ public UiObject2 getMailButton();
+
+ /**
+ * Setup expectation: Gmail is open.
+ *
+ * <p>This method will click mail button and go to the top of the mail list.
+ */
+ public void backToTopByMailButton();
+
+ /**
+ * Setup expectation: Gmail is open.
+ *
+ * <p>This method will switch mail label and go to the top of the mail list.
+ */
+ public void backToTopBySwitchLabel();
+
+ /**
+ * Setup expectations: Gmail mailbox is open.
+ *
+ * <p>This method will get a UiObject2 object for Gmail list container.
+ */
+ public UiObject2 getGmailListContainer();
+
+ /**
+ * Setup expectation: Gmail is open and emails are download completed.
+ *
+ * <p>This method will check if the current view is on Gmail list page.
+ */
+ public boolean isOnGmailListPage();
}
diff --git a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGoogleCameraHelper2.java b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGoogleCameraHelper2.java
new file mode 100644
index 000000000..a94e06919
--- /dev/null
+++ b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IGoogleCameraHelper2.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.helpers;
+
+public interface IGoogleCameraHelper2 extends IAppHelper {
+
+ /** Setup expectations: Starts taking multiple photos on camera. */
+ public void takeMultiplePhotos(int count, long takePhotoDelay);
+
+ /** Setup expectations: Switch to video record mode. */
+ public void clickVideoTab();
+
+ /** Setup expectations: Click camera video button to start recording or stop recording. */
+ public void clickCameraVideoButton();
+}
diff --git a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java
index 91c518677..4ebec540e 100644
--- a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java
+++ b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java
@@ -169,4 +169,25 @@ public interface IPhotosHelper extends IAppHelper {
* <p>Get the UiObject2 of photo scroll view pattern.
*/
public UiObject2 getPhotoScrollView();
+
+ /**
+ * Setup expectation: Photos is open.
+ *
+ * <p>Check if device is now in Photos main screen.
+ *
+ * @return Returns true if device is in Photos main screen, false if not.
+ */
+ public boolean isOnMainScreen();
+
+ /** Setup expectation: Disable backup mode in settings. */
+ public void disableBackupMode();
+
+ /** Setup expectation: Enable backup mode in settings. */
+ public void enableBackupMode();
+
+ /** Setup expectation: Verify backup starts uploading new pictures in settings. */
+ public void verifyContentStartedUploading();
+
+ /** Setup expectation: Remove photos app content. */
+ public void removeContent();
}
diff --git a/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/MediaCenterHelperImpl.java b/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/MediaCenterHelperImpl.java
index 93e6688a0..7d2f21c70 100644
--- a/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/MediaCenterHelperImpl.java
+++ b/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/MediaCenterHelperImpl.java
@@ -410,7 +410,9 @@ public class MediaCenterHelperImpl extends AbstractAutoStandardAppHelper
if (menuItemElements.size() == 0) {
throw new UnknownUiException("Unable to find Media drop down.");
}
- clickAndWaitForIdleScreen(menuItemElements.get(1));
+ // Media menu drop down is the last item in Media App Screen
+ int positionOfMenuItemDropDown = menuItemElements.size() - 1;
+ clickAndWaitForIdleScreen(menuItemElements.get(positionOfMenuItemDropDown));
}
/** {@inheritDoc} */
@@ -439,4 +441,48 @@ public class MediaCenterHelperImpl extends AbstractAutoStandardAppHelper
}
return true;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void openApp(String appName) {
+ SystemClock.sleep(SHORT_RESPONSE_WAIT_MS); // to avoid stale object error
+ UiObject2 app = scrollAndFindUiObject(By.text(appName));
+ if (app != null) {
+ clickAndWaitForIdleScreen(app);
+ } else {
+ throw new IllegalStateException(String.format("App %s cannot be found", appName));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void openMediaAppSettingsPage() {
+ List<UiObject2> mediaAppMenuItem = findUiObjects(getResourceFromConfig(
+ AutoConfigConstants.MEDIA_CENTER,
+ AutoConfigConstants.MEDIA_APP,
+ AutoConfigConstants.MEDIA_APP_DROP_DOWN_MENU));
+ if (mediaAppMenuItem.size() == 0) {
+ throw new UnknownUiException("Unable to find Media App menu items.");
+ }
+ // settings page is 2nd last item in Menu Item list
+ int settingsItemPosition = mediaAppMenuItem.size() - 2;
+ clickAndWaitForIdleScreen(mediaAppMenuItem.get(settingsItemPosition));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getMediaAppUserNotLoggedInErrorMessage() {
+ UiObject2 noLoginMsg = findUiObject(getResourceFromConfig(
+ AutoConfigConstants.MEDIA_CENTER,
+ AutoConfigConstants.MEDIA_APP,
+ AutoConfigConstants.MEDIA_APP_NO_LOGIN_MSG));
+ if (noLoginMsg == null) {
+ throw new UnknownUiException("Unable to find Media app error text.");
+ }
+ return noLoginMsg.getText();
+ }
}
diff --git a/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/TestMediaAppHelperImpl.java b/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/TestMediaAppHelperImpl.java
new file mode 100644
index 000000000..c74ec9924
--- /dev/null
+++ b/libraries/automotive-helpers/media-center-app-helper/src/android/platform/helpers/TestMediaAppHelperImpl.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.helpers;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.platform.helpers.exceptions.UnknownUiException;
+import android.support.test.uiautomator.UiObject2;
+
+import java.util.List;
+
+public class TestMediaAppHelperImpl extends AbstractAutoStandardAppHelper
+ implements IAutoTestMediaAppHelper {
+ // Wait Time
+ private static final int SHORT_RESPONSE_WAIT_MS = 1000;
+
+ public TestMediaAppHelperImpl(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void loadMediaInLocalMediaTestApp() {
+ // Open Account type
+ clickAndWait(
+ AutoConfigConstants.TEST_MEDIA_ACCOUNT_TYPE, "Account Type");
+ // Select Paid Account type
+ clickAndWait(
+ AutoConfigConstants.TEST_MEDIA_ACCOUNT_TYPE_PAID, "Account Type: Paid");
+ // open Root node type
+ clickAndWait(
+ AutoConfigConstants.TEST_MEDIA_ROOT_NODE_TYPE, "Root node type");
+ // select Browsable content
+ clickAndWait(AutoConfigConstants.TEST_MEDIA_ROOT_NODE_TYPE_BROWSABLE,
+ "Browsable Content");
+ // close settings
+ clickAndWait(AutoConfigConstants.TEST_MEDIA_APP_CLOSE_SETTING, "Close Settings");
+ selectSongInTestMediaApp();
+ }
+
+ private void selectSongInTestMediaApp() {
+ List<UiObject2> songList = findUiObjects(getResourceFromConfig(
+ AutoConfigConstants.MEDIA_CENTER,
+ AutoConfigConstants.MEDIA_APP,
+ AutoConfigConstants.MEDIA_SONGS_LIST));
+ if (songList.size() == 0) {
+ throw new UnknownUiException("Unable to find Songs in the Test Media App.");
+ }
+ clickAndWaitForIdleScreen(songList.get(1));
+ SystemClock.sleep(SHORT_RESPONSE_WAIT_MS);
+ // minimize songs
+ UiObject2 goBackToSongsList = findUiObject(getResourceFromConfig(
+ AutoConfigConstants.MEDIA_CENTER,
+ AutoConfigConstants.MEDIA_APP,
+ AutoConfigConstants.MEDIA_APP_NAVIGATION_ICON));
+ clickAndWaitForIdleScreen(goBackToSongsList);
+ SystemClock.sleep(SHORT_RESPONSE_WAIT_MS);
+ }
+
+ private void clickAndWait(String autoConfigConstants, String fieldName) {
+ UiObject2 mediaTestAppField = findUiObject(getResourceFromConfig(
+ AutoConfigConstants.MEDIA_CENTER,
+ AutoConfigConstants.TEST_MEDIA_APP,
+ autoConfigConstants));
+ if (mediaTestAppField == null) {
+ throw new UnknownUiException("Unable to find Test Media App field: " + fieldName);
+ }
+ clickAndWaitForIdleScreen(mediaTestAppField);
+ SystemClock.sleep(SHORT_RESPONSE_WAIT_MS);
+ }
+}
diff --git a/libraries/automotive-helpers/notifications-app-helper/src/android/platform/helpers/AutoNotificationMockingHelperImpl.java b/libraries/automotive-helpers/notifications-app-helper/src/android/platform/helpers/AutoNotificationMockingHelperImpl.java
index c35620044..a92ad3467 100644
--- a/libraries/automotive-helpers/notifications-app-helper/src/android/platform/helpers/AutoNotificationMockingHelperImpl.java
+++ b/libraries/automotive-helpers/notifications-app-helper/src/android/platform/helpers/AutoNotificationMockingHelperImpl.java
@@ -70,11 +70,6 @@ public class AutoNotificationMockingHelperImpl extends AbstractAutoStandardAppHe
getResourceFromConfig(
AutoConfigConstants.NOTIFICATIONS,
AutoConfigConstants.EXPANDED_NOTIFICATIONS_SCREEN,
- AutoConfigConstants.APP_NAME));
- NOTIFICATION_REQUIRED_FIELDS.add(
- getResourceFromConfig(
- AutoConfigConstants.NOTIFICATIONS,
- AutoConfigConstants.EXPANDED_NOTIFICATIONS_SCREEN,
AutoConfigConstants.NOTIFICATION_TITLE));
NOTIFICATION_REQUIRED_FIELDS.add(
getResourceFromConfig(
diff --git a/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoConfigConstants.java b/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoConfigConstants.java
index 161ac43e2..a5cf1edc0 100644
--- a/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoConfigConstants.java
+++ b/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoConfigConstants.java
@@ -235,6 +235,7 @@ public class AutoConfigConstants {
// Media Center Screen
public static final String MEDIA_CENTER_SCREEN = "MEDIA_CENTER_SCREEN";
public static final String PLAY_PAUSE_BUTTON = "PLAY_PAUSE_BUTTON";
+ public static final String MEDIA_SONGS_LIST = "MEDIA_SONGS_LIST";
// NEXT_BUTTON from Account Settings
public static final String PREVIOUS_BUTTON = "PREVIOUS_BUTTON";
public static final String SHUFFLE_BUTTON = "SHUFFLE_BUTTON";
@@ -253,4 +254,14 @@ public class AutoConfigConstants {
public static final String MEDIA_APP = "MEDIA_APP";
public static final String MEDIA_APP_TITLE = "MEDIA_APP_TITLE";
public static final String MEDIA_APP_DROP_DOWN_MENU = "MEDIA_APP_DROP_DOWN_MENU";
+ public static final String MEDIA_APP_NAVIGATION_ICON = "MEDIA_APP_NAVIGATION_ICON";
+ public static final String MEDIA_APP_NO_LOGIN_MSG = "MEDIA_APP_NO_LOGIN_MSG";
+ // Test Media App
+ public static final String TEST_MEDIA_APP = "TEST_MEDIA_APP";
+ public static final String TEST_MEDIA_ACCOUNT_TYPE = "TEST_MEDIA_ACCOUNT_TYPE";
+ public static final String TEST_MEDIA_ACCOUNT_TYPE_PAID = "TEST_MEDIA_ACCOUNT_TYPE_PAID";
+ public static final String TEST_MEDIA_ROOT_NODE_TYPE = "TEST_MEDIA_ROOT_NODE_TYPE";
+ public static final String TEST_MEDIA_ROOT_NODE_TYPE_BROWSABLE =
+ "TEST_MEDIA_ROOT_NODE_TYPE_BROWSABLE";
+ public static final String TEST_MEDIA_APP_CLOSE_SETTING = "TEST_MEDIA_APP_CLOSE_SETTING";
}
diff --git a/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoMediaCenterConfigUtility.java b/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoMediaCenterConfigUtility.java
index f7deeae79..4368d2607 100644
--- a/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoMediaCenterConfigUtility.java
+++ b/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoMediaCenterConfigUtility.java
@@ -32,6 +32,9 @@ public class AutoMediaCenterConfigUtility implements IAutoConfigUtility {
// Car Launcher For Reference Device
private static final String CAR_LAUNCHER_PACKAGE = "com.android.car.carlauncher";
+ // TEST Media App Package
+ private static final String TEST_MEDIA_APP_PACKAGE = "com.android.car.media.testmediaapp";
+
// Config Map
private Map<String, AutoConfiguration> mMediaCenterConfigMap;
@@ -164,6 +167,9 @@ public class AutoMediaCenterConfigUtility implements IAutoConfigUtility {
// Default Media Apps UI Config
loadDefaultMediaApp(mMediaCenterConfigMap);
+
+ // Load Test Media app UI Config
+ loadDefaultTestMediaApp(mMediaCenterConfigMap);
}
private void loadDefaultMediaCenterAppConfig(Map<String, String> mApplicationConfigMap) {
@@ -273,7 +279,50 @@ public class AutoMediaCenterConfigUtility implements IAutoConfigUtility {
new AutoConfigResource(
AutoConfigConstants.RESOURCE_ID,
"car_ui_toolbar_title", MEDIA_CENTER_PACKAGE));
+ mediaAppConfiguration.addResource(
+ AutoConfigConstants.MEDIA_SONGS_LIST,
+ new AutoConfigResource(
+ AutoConfigConstants.RESOURCE_ID,
+ "item_container", MEDIA_CENTER_PACKAGE));
+ mediaAppConfiguration.addResource(
+ AutoConfigConstants.MEDIA_APP_NAVIGATION_ICON,
+ new AutoConfigResource(
+ AutoConfigConstants.RESOURCE_ID,
+ "car_ui_toolbar_nav_icon_container", MEDIA_CENTER_PACKAGE));
+ mediaAppConfiguration.addResource(
+ AutoConfigConstants.MEDIA_APP_NO_LOGIN_MSG,
+ new AutoConfigResource(
+ AutoConfigConstants.RESOURCE_ID,
+ "error_message", MEDIA_CENTER_PACKAGE));
mMediaCenterConfigMap.put(
AutoConfigConstants.MEDIA_APP, mediaAppConfiguration);
}
+
+ private void loadDefaultTestMediaApp(
+ Map<String, AutoConfiguration> mMediaCenterConfigMap) {
+ AutoConfiguration testMediaAppConfiguration = new AutoConfiguration();
+ testMediaAppConfiguration.addResource(
+ AutoConfigConstants.TEST_MEDIA_ACCOUNT_TYPE,
+ new AutoConfigResource(
+ AutoConfigConstants.TEXT, "Account Type"));
+ testMediaAppConfiguration.addResource(
+ AutoConfigConstants.TEST_MEDIA_ACCOUNT_TYPE_PAID,
+ new AutoConfigResource(
+ AutoConfigConstants.TEXT, "Paid"));
+ testMediaAppConfiguration.addResource(
+ AutoConfigConstants.TEST_MEDIA_ROOT_NODE_TYPE,
+ new AutoConfigResource(
+ AutoConfigConstants.TEXT, "Root node type"));
+ testMediaAppConfiguration.addResource(
+ AutoConfigConstants.TEST_MEDIA_ROOT_NODE_TYPE_BROWSABLE,
+ new AutoConfigResource(
+ AutoConfigConstants.TEXT, "Only browse-able content"));
+ testMediaAppConfiguration.addResource(
+ AutoConfigConstants.TEST_MEDIA_APP_CLOSE_SETTING,
+ new AutoConfigResource(
+ AutoConfigConstants.RESOURCE_ID,
+ "close_target", TEST_MEDIA_APP_PACKAGE));
+ mMediaCenterConfigMap.put(
+ AutoConfigConstants.TEST_MEDIA_APP, testMediaAppConfiguration);
+ }
}
diff --git a/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoNotificationsConfigUtility.java b/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoNotificationsConfigUtility.java
index c04c76317..8447e7f27 100644
--- a/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoNotificationsConfigUtility.java
+++ b/libraries/automotive-helpers/utility-helper/src/android/platform/helpers/AutoNotificationsConfigUtility.java
@@ -183,11 +183,9 @@ public class AutoNotificationsConfigUtility implements IAutoConfigUtility {
expandedNotificationsConfig.addResource(
AutoConfigConstants.APP_ICON,
new AutoConfigResource(
- AutoConfigConstants.RESOURCE_ID, "app_icon", SYSTEM_UI_PACKAGE));
- expandedNotificationsConfig.addResource(
- AutoConfigConstants.APP_NAME,
- new AutoConfigResource(
- AutoConfigConstants.RESOURCE_ID, "header_text", SYSTEM_UI_PACKAGE));
+ AutoConfigConstants.RESOURCE_ID,
+ "notification_body_icon",
+ SYSTEM_UI_PACKAGE));
expandedNotificationsConfig.addResource(
AutoConfigConstants.NOTIFICATION_TITLE,
new AutoConfigResource(
diff --git a/libraries/collectors-helper/lyric/src/com/android/helpers/LyricMemProfilerHelper.java b/libraries/collectors-helper/lyric/src/com/android/helpers/LyricMemProfilerHelper.java
new file mode 100644
index 000000000..8647039e0
--- /dev/null
+++ b/libraries/collectors-helper/lyric/src/com/android/helpers/LyricMemProfilerHelper.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers;
+
+import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This is a collector helper that collects the dumpsys meminfo output for specified services and
+ * puts them into files.
+ */
+public class LyricMemProfilerHelper implements ICollectorHelper<Integer> {
+
+ private static final String TAG = LyricMemProfilerHelper.class.getSimpleName();
+
+ private static final String PID_CMD = "pgrep -f -o ";
+
+ private static final String DUMPSYS_MEMINFO_CMD = "dumpsys meminfo -s ";
+
+ private static final String DMABUF_DUMP_CMD = "dmabuf_dump";
+
+ private static final int MIN_PROFILE_PERIOD_MS = 100;
+
+ String mPidName = "camera.provider@";
+
+ // Extract value of "Native Heap:" and "TOTAL PSS:" from command: "dumpsys meminfo -s [pid]"
+ // example of "dumpsys meminfo -s [pid]":
+ // Applications Memory Usage (in Kilobytes):
+ // Uptime: 2649336 Realtime: 3041976
+ // ** MEMINFO in pid 14612 [android.hardwar] **
+ // App Summary
+ // Pss(KB) Rss(KB)
+ // ------ ------
+ // Java Heap: 0 0
+ // Native Heap: 377584 377584
+ // Code: 79008 117044
+ // Stack: 3364 3364
+ // Graphics: 47672 47672
+ // Private Other: 37188
+ // System: 5307
+ // Unknown: 39136
+ //
+ // TOTAL PSS: 550123 TOTAL RSS: 584800 TOTAL SWAP (KB):
+ // 0
+ //
+ // Above string example will be remove "\n" first and then extracted
+ // "377584" right after "Native Heap" and "550123" right after "TOTAL PSS"
+ // by following Regexes:
+ private static final Pattern METRIC_MEMINFO_PATTERN =
+ Pattern.compile(".+Native Heap:\\s*(\\d+)\\s*.+TOTAL PSS:\\s*(\\d+)\\s*.+");
+
+ // extrace value after "PROCESS TOTAL" in camera provider section
+ // of string from command "dmabuf_dump"
+ // Example:
+ // PROCESS TOTAL 1752 kB 1873 kB
+ // Above string example will be extracted 1752 as group(1) and 1832 as group(2)
+ private static final Pattern METRIC_DMABUF_PSS_PATTERN =
+ Pattern.compile("\\s*PROCESS TOTAL\\s*(\\d+)\\s*kB\\s*(\\d+)\\s*kB");
+
+ // Folling Regexes is for removing "\n" in string
+ private static final Pattern REMOVE_CR_PATTERN = Pattern.compile("\n");
+
+ // This Regexes is to match string format as:
+ // provider@2.7-se:[pid of provider]
+ //
+ // Use above pattern to find data section of camera provider
+ // of output string from command "dmabuf_dump"
+ private Pattern mDmabufProcPattern;
+
+ private UiDevice mUiDevice;
+
+ private String mCameraProviderPid = "";
+
+ private int mProfilePeriodMs = 0;
+
+ private Timer mTimer;
+
+ private static class MemInfo {
+ int mNativeHeap;
+ int mTotalPss;
+
+ public MemInfo(int nativeHeap, int totalPss) {
+ mNativeHeap = nativeHeap;
+ mTotalPss = totalPss;
+ }
+ }
+
+ private int mMaxNativeHeap;
+
+ private int mMaxTotalPss;
+
+ private int mMaxDmabuf;
+
+ @VisibleForTesting
+ protected UiDevice initUiDevice() {
+ return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @Override
+ public boolean startCollecting() {
+ if (null == mUiDevice) {
+ mUiDevice = initUiDevice();
+ }
+ getCameraProviderPid();
+
+ // This Regexes is to match string format as:
+ // provider@2.7-se:[pid of provider]
+ //
+ // Use following pattern to find data section of camera provider
+ // of output string from command "dmabuf_dump"
+ mDmabufProcPattern = Pattern.compile("\\s*provider@.*:" + mCameraProviderPid + "\\s*");
+
+ // To avoid frequnce of polling memory data too high and interference test case
+ // Set minimum polling period to MIN_PROFILE_PERIOD_MS (100), period profile only
+ // enable when MIN_PROFILE_PERIOD_MS <= configured polling period
+ if (MIN_PROFILE_PERIOD_MS <= mProfilePeriodMs) {
+ if (null == mTimer) {
+ mTimer = new Timer();
+ mTimer.schedule(
+ new TimerTask() {
+ @Override
+ public void run() {
+ MemInfo memInfo = processMemInfo(getMemInfoString());
+ int dmabuf = processDmabufDump(getDmabufDumpString());
+ mMaxNativeHeap = Math.max(mMaxNativeHeap, memInfo.mNativeHeap);
+ mMaxTotalPss = Math.max(mMaxTotalPss, memInfo.mTotalPss);
+ mMaxDmabuf = Math.max(mMaxDmabuf, dmabuf);
+ }
+ },
+ MIN_PROFILE_PERIOD_MS,
+ mProfilePeriodMs);
+ }
+ }
+ return true;
+ }
+
+ public void setProfilePeriodMs(int periodMs) {
+ mProfilePeriodMs = periodMs;
+ }
+
+ public void setProfilePidName(String pidName) {
+ mPidName = pidName;
+ }
+
+ @Override
+ public Map<String, Integer> getMetrics() {
+ String memInfoString = getMemInfoString();
+ String dmabufDumpString = getDmabufDumpString();
+ Map<String, Integer> metrics = processOutput(memInfoString, dmabufDumpString);
+ if (MIN_PROFILE_PERIOD_MS <= mProfilePeriodMs) {
+ metrics.put("maxNativeHeap", mMaxNativeHeap);
+ metrics.put("maxTotalPss", mMaxTotalPss);
+ metrics.put("maxDmabuf", mMaxDmabuf);
+ }
+ return metrics;
+ }
+
+ @Override
+ public boolean stopCollecting() {
+ if (null != mTimer) {
+ mTimer.cancel();
+ }
+ return true;
+ }
+
+ private MemInfo processMemInfo(String memInfoString) {
+ int nativeHeap = 0, totalPss = 0;
+ Matcher matcher =
+ METRIC_MEMINFO_PATTERN.matcher(
+ REMOVE_CR_PATTERN.matcher(memInfoString).replaceAll(""));
+ if (matcher.find()) {
+ nativeHeap = Integer.parseInt(matcher.group(1));
+ totalPss = Integer.parseInt(matcher.group(2));
+ } else {
+ Log.e(TAG, "Failed to collect Lyric Native Heap or TOTAL PSS metrics.");
+ }
+ return new MemInfo(nativeHeap, totalPss);
+ }
+
+ private int processDmabufDump(String dmabufDumpString) {
+ int dmabuf = 0;
+ Matcher matcher;
+ boolean procMatched = false;
+ for (String line : dmabufDumpString.split("\n")) {
+ if (procMatched) {
+ matcher = METRIC_DMABUF_PSS_PATTERN.matcher(line);
+ if (matcher.find()) {
+ dmabuf = Integer.parseInt(matcher.group(2));
+ break;
+ }
+ } else {
+ matcher = mDmabufProcPattern.matcher(line);
+ if (matcher.find()) {
+ procMatched = true;
+ }
+ }
+ }
+ return dmabuf;
+ }
+
+ @VisibleForTesting
+ Map<String, Integer> processOutput(String memInfoString, String dmabufDumpString) {
+ Map<String, Integer> metrics = new HashMap<>();
+ MemInfo memInfo = processMemInfo(memInfoString);
+ int dmabuf = processDmabufDump(dmabufDumpString);
+ if (0 < memInfo.mNativeHeap) metrics.put("nativeHeap", memInfo.mNativeHeap);
+ if (0 < memInfo.mTotalPss) metrics.put("totalPss", memInfo.mTotalPss);
+ if (0 < dmabuf) metrics.put("dmabuf", dmabuf);
+ return metrics;
+ }
+
+ @VisibleForTesting
+ public String getCameraProviderPid() {
+ try {
+ mCameraProviderPid = mUiDevice.executeShellCommand(PID_CMD + mPidName).trim();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get camera provider PID");
+ mCameraProviderPid = "";
+ }
+ return mCameraProviderPid;
+ }
+
+ @VisibleForTesting
+ public String getMemInfoString() {
+ if (!mCameraProviderPid.isEmpty()) {
+ try {
+ String cmdString = DUMPSYS_MEMINFO_CMD + mCameraProviderPid;
+ return mUiDevice.executeShellCommand(cmdString).trim();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get Mem info string ");
+ }
+ }
+ return "";
+ }
+
+ @VisibleForTesting
+ public String getDmabufDumpString() {
+ if (!mCameraProviderPid.isEmpty()) {
+ try {
+ final int minDmabufStringLen = 100;
+ final int maxDmabufRetryCount = 3;
+ String dmabufString;
+ for (int retryCount = 0; retryCount < maxDmabufRetryCount; retryCount++) {
+ dmabufString = mUiDevice.executeShellCommand(DMABUF_DUMP_CMD).trim();
+ // "dmabuf_dump" may not get dmabuf size information but get following string:
+ // "debugfs entry for dmabuf not available, using /proc/<pid>/fdinfo instead"
+ // Here use string length to detected above condition and retry.
+ // Normal dmabuf size string should larger than 100 characters.
+ if (minDmabufStringLen < dmabufString.length()) {
+ return dmabufString;
+ }
+ Log.w(TAG, "dmabuf_dump return abnormal:" + dmabufString + ",retry");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get DMA buf dump string");
+ }
+ }
+ return "";
+ }
+}
diff --git a/libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricMemProfilerHelperTest.java b/libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricMemProfilerHelperTest.java
new file mode 100644
index 000000000..41675bfec
--- /dev/null
+++ b/libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricMemProfilerHelperTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.util.Map;
+import java.io.IOException;
+
+/**
+ * Android unit test for {@link LyricMemProfilerHelper}
+ *
+ * <p>To run: atest CollectorsHelperTest:com.android.helpers.LyricMemProfilerHelperTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class LyricMemProfilerHelperTest {
+ private static final String TAG = LyricMemProfilerHelperTest.class.getSimpleName();
+
+ private @Mock UiDevice mUiDevice;
+
+ private int mMemInfoCmdCounter = 0;
+
+ private int mDmabufCmdCounter = 0;
+
+ private static final int MOCK_NATIVE_HEAP = 100;
+
+ private static final int MOCK_TOTAL_PSS = 200;
+
+ private static final int MOCK_DMABUF = 500;
+
+ private String genMemInfoString(int nativeHeap, int totalPss) {
+ return ".Native Heap:" + nativeHeap + " TOTAL PSS:" + totalPss + " .";
+ }
+
+ private String genDmabufString(int dmabuf, int pid) {
+ String dmabufDumpString =
+ " provider@2.7-se:"
+ + pid
+ + "\n"
+ + " Name Rss Pss "
+ + " nr_procs Inode\n"
+ + " <unknown> 576 kB 576 kB "
+ + " 1 153387\n"
+ + " <unknown> 8 kB 8 kB "
+ + " 1 153388\n"
+ + " <unknown> 576 kB 576 kB "
+ + " 1 153389\n"
+ + " <unknown> 8 kB 8 kB "
+ + " 1 153390\n"
+ + " <unknown> 576 kB 576 kB "
+ + " 1 205579\n"
+ + " <unknown> 8 kB 8 kB "
+ + " 1 205580\n"
+ + " PROCESS TOTAL 1752 kB "
+ + dmabuf
+ + " kB\n"
+ + "----------------------\n"
+ + "dmabuf total: 1752 kB kernel_rss: 0 kB userspace_rss: 1752 kB"
+ + " userspace_pss: 1752 kB";
+
+ return dmabufDumpString;
+ }
+
+ @Before
+ public void setUp() throws Throwable {
+ MockitoAnnotations.initMocks(this);
+ // Return a fake pid for our fake processes and an empty string otherwise.
+ doAnswer(
+ (inv) -> {
+ final int pid = 1234;
+ String cmd = (String) inv.getArguments()[0];
+ if (cmd.startsWith("pgrep")) {
+ return Integer.toString(pid);
+ } else if (cmd.startsWith("dumpsys meminfo")) {
+ mMemInfoCmdCounter++;
+ if (10 == mMemInfoCmdCounter) {
+ return genMemInfoString(
+ MOCK_NATIVE_HEAP + 50,
+ MOCK_TOTAL_PSS + 50); // max value
+ } else {
+ return genMemInfoString(MOCK_NATIVE_HEAP, MOCK_TOTAL_PSS);
+ }
+ } else if (cmd.startsWith("dmabuf_dump")) {
+ mDmabufCmdCounter++;
+ if (10 == mDmabufCmdCounter) {
+ return genDmabufString(MOCK_DMABUF + 50, pid); // max value
+ } else {
+ return genDmabufString(MOCK_DMABUF, pid);
+ }
+ }
+ return "";
+ })
+ .when(mUiDevice)
+ .executeShellCommand(any());
+ }
+
+ @Test
+ public void testParsePid() {
+ LyricMemProfilerHelper helper = new LyricMemProfilerHelper();
+ String memInfoString = helper.getMemInfoString();
+ String dmabufDumpString = helper.getDmabufDumpString();
+ // memInfo and dmabufDump get empty string due to mCameraProviderPid is empty
+ assertThat(memInfoString).isEmpty();
+ assertThat(dmabufDumpString).isEmpty();
+
+ SystemClock.sleep(1000); // sleep 1 second to wait for camera provider initialize
+ helper.startCollecting();
+ memInfoString = helper.getMemInfoString();
+ dmabufDumpString = helper.getDmabufDumpString();
+ Map<String, Integer> metrics = helper.processOutput(memInfoString, dmabufDumpString);
+
+ assertThat(metrics).containsKey("nativeHeap");
+ assertThat(metrics).containsKey("totalPss");
+ assertThat(metrics).containsKey("dmabuf");
+
+ assertThat(metrics.get("nativeHeap")).isGreaterThan(0);
+ assertThat(metrics.get("totalPss")).isGreaterThan(0);
+ assertThat(metrics.get("dmabuf")).isGreaterThan(0);
+ }
+
+ @Test
+ public void testProcessOutput() {
+ LyricMemProfilerHelper helper = new LyricMemProfilerHelper();
+ helper.startCollecting();
+ String pid = helper.getCameraProviderPid();
+ String memInfoString =
+ "Applications Memory Usage (in Kilobytes):\n"
+ + "Uptime: 2649336 Realtime: 3041976\n"
+ + "** MEMINFO in pid 14612 [android.hardwar] **\n"
+ + "App Summary\n"
+ + " Pss(KB) Rss(KB)\n"
+ + " ------ ------\n"
+ + " Java Heap: 0 0\n"
+ + " Native Heap: 377584 377584\n"
+ + " Code: 79008 117044\n"
+ + " Stack: 3364 3364\n"
+ + " Graphics: 47672 47672\n"
+ + " Private Other: 37188\n"
+ + " System: 5307\n"
+ + " Unknown: 39136\n"
+ + "\n"
+ + " TOTAL PSS: 550123 TOTAL RSS: 584800 TOTAL"
+ + " SWAP (KB): 0";
+
+ String dmabufDumpString =
+ " provider@2.7-se:"
+ + pid
+ + "\n"
+ + " Name Rss Pss "
+ + " nr_procs Inode\n"
+ + " <unknown> 576 kB 576 kB "
+ + " 1 153387\n"
+ + " <unknown> 8 kB 8 kB "
+ + " 1 153388\n"
+ + " <unknown> 576 kB 576 kB "
+ + " 1 153389\n"
+ + " <unknown> 8 kB 8 kB "
+ + " 1 153390\n"
+ + " <unknown> 576 kB 576 kB "
+ + " 1 205579\n"
+ + " <unknown> 8 kB 8 kB "
+ + " 1 205580\n"
+ + " PROCESS TOTAL 1752 kB 1552 kB\n"
+ + "----------------------\n"
+ + "dmabuf total: 1752 kB kernel_rss: 0 kB userspace_rss: 1752 kB"
+ + " userspace_pss: 1752 kB";
+
+ Map<String, Integer> metrics = helper.processOutput(memInfoString, dmabufDumpString);
+ assertThat(metrics.get("nativeHeap")).isEqualTo(377584);
+ assertThat(metrics.get("totalPss")).isEqualTo(550123);
+ assertThat(metrics.get("dmabuf")).isEqualTo(1552);
+
+ metrics = helper.processOutput("", "");
+ assertThat(metrics).doesNotContainKey("nativeHeap");
+ assertThat(metrics).doesNotContainKey("totalPss");
+ assertThat(metrics).doesNotContainKey("dmabuf");
+ }
+
+ @Test
+ public void testProfilePeriod() {
+ LyricMemProfilerHelper helper = new TestableLyricMemProfilerHelper();
+ helper.setProfilePeriodMs(100);
+ helper.startCollecting();
+ SystemClock.sleep(2000);
+ Map<String, Integer> metrics = helper.getMetrics();
+ helper.stopCollecting();
+ assertThat(metrics).containsKey("nativeHeap");
+ assertThat(metrics).containsKey("totalPss");
+ assertThat(metrics).containsKey("dmabuf");
+ assertThat(metrics).containsKey("maxNativeHeap");
+ assertThat(metrics).containsKey("maxTotalPss");
+ assertThat(metrics).containsKey("maxDmabuf");
+
+ assertThat(metrics.get("nativeHeap")).isLessThan(metrics.get("maxNativeHeap"));
+ assertThat(metrics.get("totalPss")).isLessThan(metrics.get("maxTotalPss"));
+ assertThat(metrics.get("dmabuf")).isLessThan(metrics.get("maxDmabuf"));
+ }
+
+ @Test
+ public void testProfilePeriodLessThanMin() {
+ LyricMemProfilerHelper helper = new TestableLyricMemProfilerHelper();
+ InOrder inOrder = inOrder(mUiDevice);
+ helper.setProfilePeriodMs(50);
+ helper.startCollecting();
+ try {
+ inOrder.verify(mUiDevice).executeShellCommand("pgrep -f -o camera.provider@");
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to execute Shell command");
+ }
+ SystemClock.sleep(3000);
+ verifyNoMoreInteractions(mUiDevice);
+
+ Map<String, Integer> metrics = helper.getMetrics();
+ helper.stopCollecting();
+ assertThat(metrics).containsKey("nativeHeap");
+ assertThat(metrics).containsKey("totalPss");
+ assertThat(metrics).containsKey("dmabuf");
+ assertThat(metrics).doesNotContainKey("maxNativeHeap");
+ assertThat(metrics).doesNotContainKey("maxTotalPss");
+ assertThat(metrics).doesNotContainKey("maxDmabuf");
+
+ assertThat(metrics.get("nativeHeap")).isEqualTo(MOCK_NATIVE_HEAP);
+ assertThat(metrics.get("totalPss")).isEqualTo(MOCK_TOTAL_PSS);
+ assertThat(metrics.get("dmabuf")).isEqualTo(MOCK_DMABUF);
+ }
+
+ @Test
+ public void testSetNewPidName() {
+ LyricMemProfilerHelper helper = new TestableLyricMemProfilerHelper();
+ InOrder inOrder = inOrder(mUiDevice);
+ final String newPidName = "new.camera.name";
+ helper.setProfilePidName(newPidName);
+ helper.startCollecting();
+ try {
+ inOrder.verify(mUiDevice).executeShellCommand("pgrep -f -o " + newPidName);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to execute Shell command");
+ }
+ }
+
+ private final class TestableLyricMemProfilerHelper extends LyricMemProfilerHelper {
+ @Override
+ protected UiDevice initUiDevice() {
+ return mUiDevice;
+ }
+ }
+}
diff --git a/libraries/collectors-helper/statsd/src/com/android/helpers/CrashHelper.java b/libraries/collectors-helper/statsd/src/com/android/helpers/CrashHelper.java
index c6f14119b..99103e26f 100644
--- a/libraries/collectors-helper/statsd/src/com/android/helpers/CrashHelper.java
+++ b/libraries/collectors-helper/statsd/src/com/android/helpers/CrashHelper.java
@@ -66,8 +66,12 @@ public class CrashHelper implements ICollectorHelper<Integer> {
appCrashResultMap.put(TOTAL_PREFIX + EVENT_NATIVE_CRASH, 0);
appCrashResultMap.put(TOTAL_PREFIX + EVENT_ANR, 0);
for (StatsLog.EventMetricData dataItem : eventMetricData) {
- if (dataItem.atom.hasAppCrashOccurred()) {
- AtomsProto.AppCrashOccurred appCrashAtom = dataItem.atom.getAppCrashOccurred();
+ AtomsProto.Atom atom = dataItem.atom;
+ if (atom == null) {
+ atom = dataItem.aggregatedAtomInfo.atom;
+ }
+ if (atom.hasAppCrashOccurred()) {
+ AtomsProto.AppCrashOccurred appCrashAtom = atom.getAppCrashOccurred();
String eventType = appCrashAtom.eventType;
String pkgName = appCrashAtom.packageName;
int foregroundState = appCrashAtom.foregroundState;
@@ -84,8 +88,8 @@ public class CrashHelper implements ICollectorHelper<Integer> {
MetricUtility.constructKey(
eventType, pkgName, String.valueOf(foregroundState));
MetricUtility.addMetric(detailKey, appCrashResultMap);
- } else if (dataItem.atom.hasAnrOccurred()) {
- AtomsProto.ANROccurred anrAtom = dataItem.atom.getAnrOccurred();
+ } else if (atom.hasAnrOccurred()) {
+ AtomsProto.ANROccurred anrAtom = atom.getAnrOccurred();
String processName = anrAtom.processName;
String reason = anrAtom.reason;
int foregoundState = anrAtom.foregroundState;
diff --git a/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdHelper.java b/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdHelper.java
index f5e32942f..9b69a832c 100644
--- a/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdHelper.java
+++ b/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdHelper.java
@@ -182,6 +182,21 @@ public class StatsdHelper {
return config;
}
+ /** Returns accumulated StatsdStats. */
+ public com.android.os.nano.StatsLog.StatsdStatsReport getStatsdStatsReport() {
+ com.android.os.nano.StatsLog.StatsdStatsReport report =
+ new com.android.os.nano.StatsLog.StatsdStatsReport();
+ try {
+ adoptShellIdentity();
+ byte[] serializedReports = getStatsManager().getStatsMetadata();
+ report = com.android.os.nano.StatsLog.StatsdStatsReport.parseFrom(serializedReports);
+ dropShellIdentity();
+ } catch (InvalidProtocolBufferNanoException | StatsUnavailableException se) {
+ Log.e(LOG_TAG, "Retrieving StatsdStats report failed.", se);
+ }
+ return report;
+ }
+
/** Returns the list of EventMetricData tracked under the config. */
public List<com.android.os.nano.StatsLog.EventMetricData> getEventMetrics() {
List<com.android.os.nano.StatsLog.EventMetricData> eventData = new ArrayList<>();
@@ -196,7 +211,7 @@ public class StatsdHelper {
dropShellIdentity();
}
} catch (InvalidProtocolBufferNanoException | StatsUnavailableException se) {
- Log.e(LOG_TAG, "Retreiving event metrics failed.", se);
+ Log.e(LOG_TAG, "Retrieving event metrics failed.", se);
return eventData;
}
@@ -230,7 +245,7 @@ public class StatsdHelper {
dropShellIdentity();
}
} catch (InvalidProtocolBufferNanoException | StatsUnavailableException se) {
- Log.e(LOG_TAG, "Retreiving gauge metrics failed.", se);
+ Log.e(LOG_TAG, "Retrieving gauge metrics failed.", se);
return gaugeData;
}
diff --git a/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdStatsHelper.java b/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdStatsHelper.java
new file mode 100644
index 000000000..427849487
--- /dev/null
+++ b/libraries/collectors-helper/statsd/src/com/android/helpers/StatsdStatsHelper.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.os.nano.StatsLog;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * StatsdStatsHelper consist of helper methods to set the Statsd metadata collection and retrieve
+ * the necessary information from statsd.
+ */
+public class StatsdStatsHelper implements ICollectorHelper<Long> {
+
+ static final String STATSDSTATS_PREFIX = "statsdstats";
+ static final String ATOM_STATS_PREFIX = "atom_stats";
+ static final String MATCHER_STATS_PREFIX = "matcher_stats";
+ static final String CONDITION_STATS_PREFIX = "condition_stats";
+ static final String METRIC_STATS_PREFIX = "metric_stats";
+ static final String ALERT_STATS_PREFIX = "alert_stats";
+ static final String CONFIG_STATS_PREFIX = "config_stats";
+ static final String ANOMALY_ALARM_STATS_PREFIX = "anomaly_alarm_stats";
+ static final String PULLED_ATOM_STATS_PREFIX = "pulled_atom_stats";
+ static final String ATOM_METRIC_STATS_PREFIX = "atom_metric_stats";
+ static final String DETECTED_LOG_LOSS_STATS_PREFIX = "detected_log_loss_stats";
+ static final String EVENT_QUEUE_OVERFLOW_STATS_PREFIX = "event_queue_overflow_stats";
+
+ interface IStatsdHelper {
+ StatsLog.StatsdStatsReport getStatsdStatsReport();
+ }
+
+ private static class DefaultStatsdHelper implements IStatsdHelper {
+
+ private StatsdHelper mStatsdHelper = new StatsdHelper();
+
+ @Override
+ public StatsLog.StatsdStatsReport getStatsdStatsReport() {
+ return mStatsdHelper.getStatsdStatsReport();
+ }
+ }
+
+ private IStatsdHelper mStatsdHelper = new DefaultStatsdHelper();
+
+ public StatsdStatsHelper() {}
+
+ /**
+ * Constructor to simulate an externally provided statsd helper instance. Should not be used
+ * except for testing.
+ */
+ @VisibleForTesting
+ StatsdStatsHelper(IStatsdHelper helper) {
+ mStatsdHelper = helper;
+ }
+
+ /** Resets statsd metadata */
+ @Override
+ public boolean startCollecting() {
+ // TODO: http://b/204890512 implement metadata reset
+ return true;
+ }
+
+ /** Collect the statsd metadata accumulated during the test run. */
+ @Override
+ public Map<String, Long> getMetrics() {
+ Map<String, Long> resultMap = new HashMap<>();
+
+ final StatsLog.StatsdStatsReport report = mStatsdHelper.getStatsdStatsReport();
+ populateAtomStats(report.atomStats, resultMap);
+ populateConfigStats(report.configStats, resultMap);
+ populateAnomalyAlarmStats(report.anomalyAlarmStats, resultMap);
+ populatePulledAtomStats(report.pulledAtomStats, resultMap);
+ populateAtomMetricStats(report.atomMetricStats, resultMap);
+ populateDetectedLogLossStats(report.detectedLogLoss, resultMap);
+ populateEventQueueOverflowStats(report.queueOverflow, resultMap);
+
+ return resultMap;
+ }
+
+ @Override
+ public boolean stopCollecting() {
+ return true;
+ }
+
+ private static void populateAtomStats(
+ StatsLog.StatsdStatsReport.AtomStats[] atomStats, Map<String, Long> resultMap) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(STATSDSTATS_PREFIX, ATOM_STATS_PREFIX);
+
+ for (final StatsLog.StatsdStatsReport.AtomStats dataItem : atomStats) {
+ final String metricKeyPrefixWithTag =
+ MetricUtility.constructKey(metricKeyPrefix, String.valueOf(dataItem.tag));
+
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "count"),
+ Long.valueOf(dataItem.count));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "error_count"),
+ Long.valueOf(dataItem.errorCount));
+ }
+ }
+
+ private static void populateConfigStats(
+ StatsLog.StatsdStatsReport.ConfigStats[] configStats, Map<String, Long> resultMap) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(STATSDSTATS_PREFIX, CONFIG_STATS_PREFIX);
+
+ for (final StatsLog.StatsdStatsReport.ConfigStats dataItem : configStats) {
+ final String metricKeyPrefixWithTag =
+ MetricUtility.constructKey(metricKeyPrefix, String.valueOf(dataItem.id));
+
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "metric_count"),
+ Long.valueOf(dataItem.metricCount));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "condition_count"),
+ Long.valueOf(dataItem.conditionCount));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "matcher_count"),
+ Long.valueOf(dataItem.matcherCount));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "alert_count"),
+ Long.valueOf(dataItem.alertCount));
+
+ populateMatcherStats(dataItem.matcherStats, resultMap, metricKeyPrefixWithTag);
+ populateConditionStats(dataItem.conditionStats, resultMap, metricKeyPrefixWithTag);
+ populateMetricStats(dataItem.metricStats, resultMap, metricKeyPrefixWithTag);
+ populateAlertStats(dataItem.alertStats, resultMap, metricKeyPrefixWithTag);
+ }
+ }
+
+ private static void populateMetricStats(
+ StatsLog.StatsdStatsReport.MetricStats[] stats,
+ Map<String, Long> resultMap,
+ String metricKeyPrefix) {
+ for (final StatsLog.StatsdStatsReport.MetricStats dataItem : stats) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ METRIC_STATS_PREFIX,
+ String.valueOf(dataItem.id),
+ "max_tuple_counts");
+ resultMap.put(metricKey, Long.valueOf(dataItem.maxTupleCounts));
+ }
+ }
+
+ private static void populateConditionStats(
+ StatsLog.StatsdStatsReport.ConditionStats[] stats,
+ Map<String, Long> resultMap,
+ String metricKeyPrefix) {
+ for (final StatsLog.StatsdStatsReport.ConditionStats dataItem : stats) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ CONDITION_STATS_PREFIX,
+ String.valueOf(dataItem.id),
+ "max_tuple_counts");
+ resultMap.put(metricKey, Long.valueOf(dataItem.maxTupleCounts));
+ }
+ }
+
+ private static void populateMatcherStats(
+ StatsLog.StatsdStatsReport.MatcherStats[] stats,
+ Map<String, Long> resultMap,
+ String metricKeyPrefix) {
+ for (final StatsLog.StatsdStatsReport.MatcherStats dataItem : stats) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ MATCHER_STATS_PREFIX,
+ String.valueOf(dataItem.id),
+ "matched_times");
+ resultMap.put(metricKey, Long.valueOf(dataItem.matchedTimes));
+ }
+ }
+
+ private static void populateAlertStats(
+ StatsLog.StatsdStatsReport.AlertStats[] stats,
+ Map<String, Long> resultMap,
+ String metricKeyPrefix) {
+ for (final StatsLog.StatsdStatsReport.AlertStats dataItem : stats) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ ALERT_STATS_PREFIX,
+ String.valueOf(dataItem.id),
+ "alerted_times");
+ resultMap.put(metricKey, Long.valueOf(dataItem.alertedTimes));
+ }
+ }
+
+ private static void populateAnomalyAlarmStats(
+ StatsLog.StatsdStatsReport.AnomalyAlarmStats anomalyAlarmStats,
+ Map<String, Long> resultMap) {
+ if (anomalyAlarmStats == null) {
+ return;
+ }
+ final String metricKey =
+ MetricUtility.constructKey(
+ STATSDSTATS_PREFIX, ANOMALY_ALARM_STATS_PREFIX, "alarms_registered");
+ resultMap.put(metricKey, Long.valueOf(anomalyAlarmStats.alarmsRegistered));
+ }
+
+ private static void populatePulledAtomStats(
+ StatsLog.StatsdStatsReport.PulledAtomStats[] pulledAtomStats,
+ Map<String, Long> resultMap) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(STATSDSTATS_PREFIX, PULLED_ATOM_STATS_PREFIX);
+
+ for (final StatsLog.StatsdStatsReport.PulledAtomStats dataItem : pulledAtomStats) {
+ final String metricKeyWithTag =
+ MetricUtility.constructKey(metricKeyPrefix, String.valueOf(dataItem.atomId));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "total_pull"),
+ Long.valueOf(dataItem.totalPull));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "total_pull_from_cache"),
+ Long.valueOf(dataItem.totalPullFromCache));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "min_pull_interval_sec"),
+ Long.valueOf(dataItem.minPullIntervalSec));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "average_pull_time_nanos"),
+ Long.valueOf(dataItem.averagePullTimeNanos));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "max_pull_time_nanos"),
+ Long.valueOf(dataItem.maxPullTimeNanos));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "average_pull_delay_nanos"),
+ Long.valueOf(dataItem.averagePullDelayNanos));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "data_error"),
+ Long.valueOf(dataItem.dataError));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "pull_timeout"),
+ Long.valueOf(dataItem.pullTimeout));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "pull_exceed_max_delay"),
+ Long.valueOf(dataItem.pullExceedMaxDelay));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "pull_failed"),
+ Long.valueOf(dataItem.pullFailed));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "empty_data"),
+ Long.valueOf(dataItem.emptyData));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "pull_registered_count"),
+ Long.valueOf(dataItem.registeredCount));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "pull_unregistered_count"),
+ Long.valueOf(dataItem.unregisteredCount));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "atom_error_count"),
+ Long.valueOf(dataItem.atomErrorCount));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "binder_call_failed"),
+ Long.valueOf(dataItem.binderCallFailed));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "failed_uid_provider_not_found"),
+ Long.valueOf(dataItem.failedUidProviderNotFound));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyWithTag, "puller_not_found"),
+ Long.valueOf(dataItem.pullerNotFound));
+ }
+ }
+
+ private static void populateAtomMetricStats(
+ StatsLog.StatsdStatsReport.AtomMetricStats[] atomMetricStats,
+ Map<String, Long> resultMap) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(STATSDSTATS_PREFIX, ATOM_METRIC_STATS_PREFIX);
+
+ for (StatsLog.StatsdStatsReport.AtomMetricStats dataItem : atomMetricStats) {
+ final String metricKeyPrefixWithTag =
+ MetricUtility.constructKey(metricKeyPrefix, String.valueOf(dataItem.metricId));
+
+ resultMap.put(
+ MetricUtility.constructKey(
+ metricKeyPrefixWithTag, "hard_dimension_limit_reached"),
+ dataItem.hardDimensionLimitReached);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "late_log_event_skipped"),
+ dataItem.lateLogEventSkipped);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "skipped_forward_buckets"),
+ dataItem.skippedForwardBuckets);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "bad_value_type"),
+ dataItem.badValueType);
+ resultMap.put(
+ MetricUtility.constructKey(
+ metricKeyPrefixWithTag, "condition_change_in_next_bucket"),
+ dataItem.conditionChangeInNextBucket);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "invalidated_bucket"),
+ dataItem.invalidatedBucket);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "bucket_dropped"),
+ dataItem.bucketDropped);
+ resultMap.put(
+ MetricUtility.constructKey(
+ metricKeyPrefixWithTag, "min_bucket_boundary_delay_ns"),
+ dataItem.minBucketBoundaryDelayNs);
+ resultMap.put(
+ MetricUtility.constructKey(
+ metricKeyPrefixWithTag, "max_bucket_boundary_delay_ns"),
+ dataItem.maxBucketBoundaryDelayNs);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "bucket_unknown_condition"),
+ dataItem.bucketUnknownCondition);
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "bucket_count"),
+ dataItem.bucketCount);
+ }
+ }
+
+ private static void populateDetectedLogLossStats(
+ StatsLog.StatsdStatsReport.LogLossStats[] detectedLogLoss,
+ Map<String, Long> resultMap) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(STATSDSTATS_PREFIX, DETECTED_LOG_LOSS_STATS_PREFIX);
+
+ for (final StatsLog.StatsdStatsReport.LogLossStats dataItem : detectedLogLoss) {
+ final String metricKeyPrefixWithTag =
+ MetricUtility.constructKey(
+ metricKeyPrefix, String.valueOf(dataItem.detectedTimeSec));
+
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "count"),
+ Long.valueOf(dataItem.count));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "last_error"),
+ Long.valueOf(dataItem.lastError));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "last_tag"),
+ Long.valueOf(dataItem.lastTag));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "uid"),
+ Long.valueOf(dataItem.uid));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefixWithTag, "pid"),
+ Long.valueOf(dataItem.pid));
+ }
+ }
+
+ private static void populateEventQueueOverflowStats(
+ StatsLog.StatsdStatsReport.EventQueueOverflow queueOverflow,
+ Map<String, Long> resultMap) {
+ if (queueOverflow == null) {
+ return;
+ }
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(STATSDSTATS_PREFIX, EVENT_QUEUE_OVERFLOW_STATS_PREFIX);
+
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefix, "count"),
+ Long.valueOf(queueOverflow.count));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefix, "max_queue_history_nanos"),
+ Long.valueOf(queueOverflow.maxQueueHistoryNs));
+ resultMap.put(
+ MetricUtility.constructKey(metricKeyPrefix, "min_queue_history_nanos"),
+ Long.valueOf(queueOverflow.minQueueHistoryNs));
+ }
+
+}
diff --git a/libraries/collectors-helper/statsd/src/com/android/helpers/UiInteractionFrameInfoHelper.java b/libraries/collectors-helper/statsd/src/com/android/helpers/UiInteractionFrameInfoHelper.java
index 3f0fe1213..5a441fe02 100644
--- a/libraries/collectors-helper/statsd/src/com/android/helpers/UiInteractionFrameInfoHelper.java
+++ b/libraries/collectors-helper/statsd/src/com/android/helpers/UiInteractionFrameInfoHelper.java
@@ -112,6 +112,11 @@ public class UiInteractionFrameInfoHelper implements ICollectorHelper<StringBuil
frameInfoMap);
addMetric(
+ constructKey(KEY_PREFIX_CUJ, interactionType, "app_missed_frames"),
+ makeLogFriendly(uiInteractionFrameInfoReported.appMissedFrames),
+ frameInfoMap);
+
+ addMetric(
constructKey(KEY_PREFIX_CUJ, interactionType, SUFFIX_MAX_FRAME_MS),
makeLogFriendly(
uiInteractionFrameInfoReported.maxFrameTimeNanos / 1000000.0),
diff --git a/libraries/collectors-helper/statsd/test/src/com/android/helpers/StatsdStatsHelperTest.java b/libraries/collectors-helper/statsd/test/src/com/android/helpers/StatsdStatsHelperTest.java
new file mode 100644
index 000000000..d51f83dd5
--- /dev/null
+++ b/libraries/collectors-helper/statsd/test/src/com/android/helpers/StatsdStatsHelperTest.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.helpers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.nano.StatsLog;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+/**
+ * Android Unit tests for {@link StatsdStatsHelper}.
+ *
+ * <p>To run: Disable SELinux: adb shell setenforce 0; if this fails with "permission denied", try
+ * Build and install Development apk. "adb shell su 0 setenforce 0" atest
+ * CollectorsHelperTest:com.android.helpers.StatsdStatsTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class StatsdStatsHelperTest {
+
+ private static class TestNonEmptyStatsdHelper implements StatsdStatsHelper.IStatsdHelper {
+
+ StatsLog.StatsdStatsReport testReport = new StatsLog.StatsdStatsReport();
+
+ static final int ATOM_STATS_COUNT = 2;
+ static final int CONFIG_STATS_COUNT = 1;
+ static final int CONFIG_STATS_METRIC_COUNT = 2;
+ static final int CONFIG_STATS_CONDITION_COUNT = 2;
+ static final int CONFIG_STATS_ALERT_COUNT = 2;
+ static final int CONFIG_STATS_MATCHER_COUNT = 2;
+ static final int PULLED_ATOM_STATS_COUNT = 2;
+ static final int ATOM_METRIC_STATS_COUNT = 1;
+ static final int DETECTED_LOG_LOSS_STATS_COUNT = 1;
+
+ public TestNonEmptyStatsdHelper() {
+ populateAtomStatsTestData(testReport);
+ populateConfigStatsTestData(testReport);
+ populateAnomalyAlarmStatsTestData(testReport);
+ populatePulledAtomStatsTestData(testReport);
+ populateAtomMetricStatsTestData(testReport);
+ populateDetectedLogLossStatsTestData(testReport);
+ populateEventQueueOverflowStatsTestData(testReport);
+ }
+
+ private static void populateAtomStatsTestData(StatsLog.StatsdStatsReport testReport) {
+ testReport.atomStats = new StatsLog.StatsdStatsReport.AtomStats[ATOM_STATS_COUNT];
+
+ for (int i = 0; i < ATOM_STATS_COUNT; i++) {
+ testReport.atomStats[i] = new StatsLog.StatsdStatsReport.AtomStats();
+ int fieldValue = i + 1;
+ testReport.atomStats[i].tag = fieldValue++;
+ testReport.atomStats[i].count = fieldValue++;
+ testReport.atomStats[i].errorCount = fieldValue++;
+ }
+ }
+
+ private static void populateConfigStatsTestData(StatsLog.StatsdStatsReport testReport) {
+ testReport.configStats = new StatsLog.StatsdStatsReport.ConfigStats[CONFIG_STATS_COUNT];
+ for (int i = 0; i < CONFIG_STATS_COUNT; i++) {
+ testReport.configStats[i] = new StatsLog.StatsdStatsReport.ConfigStats();
+ testReport.configStats[i].id = i + 1;
+ testReport.configStats[i].metricCount = CONFIG_STATS_METRIC_COUNT;
+ testReport.configStats[i].conditionCount = CONFIG_STATS_CONDITION_COUNT;
+ testReport.configStats[i].alertCount = CONFIG_STATS_ALERT_COUNT;
+ testReport.configStats[i].matcherCount = CONFIG_STATS_MATCHER_COUNT;
+
+ testReport.configStats[i].metricStats =
+ populateConfigStatsMetricTestData(CONFIG_STATS_METRIC_COUNT);
+ testReport.configStats[i].conditionStats =
+ populateConfigStatsConditionTestData(CONFIG_STATS_CONDITION_COUNT);
+ testReport.configStats[i].matcherStats =
+ populateConfigStatsMatcherTestData(CONFIG_STATS_ALERT_COUNT);
+ testReport.configStats[i].alertStats =
+ populateConfigStatsAlertTestData(CONFIG_STATS_MATCHER_COUNT);
+ }
+ }
+
+ private static StatsLog.StatsdStatsReport.AlertStats[] populateConfigStatsAlertTestData(
+ int configStatsAlertCount) {
+ StatsLog.StatsdStatsReport.AlertStats[] alertStats =
+ new StatsLog.StatsdStatsReport.AlertStats[configStatsAlertCount];
+
+ for (int i = 0; i < configStatsAlertCount; i++) {
+ alertStats[i] = new StatsLog.StatsdStatsReport.AlertStats();
+ int fieldValue = i + 1;
+ alertStats[i].id = fieldValue++;
+ alertStats[i].alertedTimes = fieldValue++;
+ }
+
+ return alertStats;
+ }
+
+ private static StatsLog.StatsdStatsReport.MetricStats[] populateConfigStatsMetricTestData(
+ int configStatsMetricCount) {
+ StatsLog.StatsdStatsReport.MetricStats[] metricStats =
+ new StatsLog.StatsdStatsReport.MetricStats[configStatsMetricCount];
+
+ for (int i = 0; i < configStatsMetricCount; i++) {
+ metricStats[i] = new StatsLog.StatsdStatsReport.MetricStats();
+ int fieldValue = i + 1;
+ metricStats[i].id = fieldValue++;
+ metricStats[i].maxTupleCounts = fieldValue++;
+ }
+
+ return metricStats;
+ }
+
+ private static StatsLog.StatsdStatsReport.ConditionStats[]
+ populateConfigStatsConditionTestData(int configStatsConditionCount) {
+ StatsLog.StatsdStatsReport.ConditionStats[] conditionStats =
+ new StatsLog.StatsdStatsReport.ConditionStats[configStatsConditionCount];
+
+ for (int i = 0; i < configStatsConditionCount; i++) {
+ conditionStats[i] = new StatsLog.StatsdStatsReport.ConditionStats();
+ int fieldValue = i + 1;
+ conditionStats[i].id = fieldValue++;
+ conditionStats[i].maxTupleCounts = fieldValue++;
+ }
+
+ return conditionStats;
+ }
+
+ private static StatsLog.StatsdStatsReport.MatcherStats[] populateConfigStatsMatcherTestData(
+ int configStatsMatcherCount) {
+ StatsLog.StatsdStatsReport.MatcherStats[] matcherStats =
+ new StatsLog.StatsdStatsReport.MatcherStats[configStatsMatcherCount];
+
+ for (int i = 0; i < configStatsMatcherCount; i++) {
+ matcherStats[i] = new StatsLog.StatsdStatsReport.MatcherStats();
+ int fieldValue = i + 1;
+ matcherStats[i].id = fieldValue++;
+ matcherStats[i].matchedTimes = fieldValue++;
+ }
+
+ return matcherStats;
+ }
+
+ private static void populateAnomalyAlarmStatsTestData(
+ StatsLog.StatsdStatsReport testReport) {
+ testReport.anomalyAlarmStats = new StatsLog.StatsdStatsReport.AnomalyAlarmStats();
+ testReport.anomalyAlarmStats.alarmsRegistered = 1;
+ }
+
+ private static void populatePulledAtomStatsTestData(StatsLog.StatsdStatsReport testReport) {
+ testReport.pulledAtomStats =
+ new StatsLog.StatsdStatsReport.PulledAtomStats[PULLED_ATOM_STATS_COUNT];
+
+ for (int i = 0; i < PULLED_ATOM_STATS_COUNT; i++) {
+ testReport.pulledAtomStats[i] = new StatsLog.StatsdStatsReport.PulledAtomStats();
+ int fieldValue = i + 1;
+ testReport.pulledAtomStats[i].atomId = fieldValue++;
+ testReport.pulledAtomStats[i].totalPull = fieldValue++;
+ testReport.pulledAtomStats[i].totalPullFromCache = fieldValue++;
+ testReport.pulledAtomStats[i].minPullIntervalSec = fieldValue++;
+ testReport.pulledAtomStats[i].averagePullTimeNanos = fieldValue++;
+ testReport.pulledAtomStats[i].maxPullTimeNanos = fieldValue++;
+ testReport.pulledAtomStats[i].averagePullDelayNanos = fieldValue++;
+ testReport.pulledAtomStats[i].dataError = fieldValue++;
+ testReport.pulledAtomStats[i].pullTimeout = fieldValue++;
+ testReport.pulledAtomStats[i].pullExceedMaxDelay = fieldValue++;
+ testReport.pulledAtomStats[i].pullFailed = fieldValue++;
+ testReport.pulledAtomStats[i].emptyData = fieldValue++;
+ testReport.pulledAtomStats[i].registeredCount = fieldValue++;
+ testReport.pulledAtomStats[i].unregisteredCount = fieldValue++;
+ testReport.pulledAtomStats[i].atomErrorCount = fieldValue++;
+ testReport.pulledAtomStats[i].binderCallFailed = fieldValue++;
+ testReport.pulledAtomStats[i].failedUidProviderNotFound = fieldValue++;
+ testReport.pulledAtomStats[i].pullerNotFound = fieldValue++;
+ }
+ }
+
+ private static void populateAtomMetricStatsTestData(StatsLog.StatsdStatsReport testReport) {
+ testReport.atomMetricStats =
+ new StatsLog.StatsdStatsReport.AtomMetricStats[ATOM_METRIC_STATS_COUNT];
+ for (int i = 0; i < ATOM_METRIC_STATS_COUNT; i++) {
+ testReport.atomMetricStats[i] = new StatsLog.StatsdStatsReport.AtomMetricStats();
+ int fieldValue = i + 1;
+ testReport.atomMetricStats[i].metricId = fieldValue++;
+ testReport.atomMetricStats[i].hardDimensionLimitReached = fieldValue++;
+ testReport.atomMetricStats[i].lateLogEventSkipped = fieldValue++;
+ testReport.atomMetricStats[i].skippedForwardBuckets = fieldValue++;
+ testReport.atomMetricStats[i].badValueType = fieldValue++;
+ testReport.atomMetricStats[i].conditionChangeInNextBucket = fieldValue++;
+ testReport.atomMetricStats[i].invalidatedBucket = fieldValue++;
+ testReport.atomMetricStats[i].bucketDropped = fieldValue++;
+ testReport.atomMetricStats[i].minBucketBoundaryDelayNs = fieldValue++;
+ testReport.atomMetricStats[i].maxBucketBoundaryDelayNs = fieldValue++;
+ testReport.atomMetricStats[i].bucketUnknownCondition = fieldValue++;
+ testReport.atomMetricStats[i].bucketCount = fieldValue++;
+ }
+ }
+
+ private static void populateDetectedLogLossStatsTestData(
+ StatsLog.StatsdStatsReport testReport) {
+ testReport.detectedLogLoss =
+ new StatsLog.StatsdStatsReport.LogLossStats[DETECTED_LOG_LOSS_STATS_COUNT];
+
+ for (int i = 0; i < DETECTED_LOG_LOSS_STATS_COUNT; i++) {
+ testReport.detectedLogLoss[i] = new StatsLog.StatsdStatsReport.LogLossStats();
+ int fieldValue = i + 1;
+ testReport.detectedLogLoss[i].detectedTimeSec = fieldValue++;
+ testReport.detectedLogLoss[i].count = fieldValue++;
+ testReport.detectedLogLoss[i].lastError = fieldValue++;
+ testReport.detectedLogLoss[i].lastTag = fieldValue++;
+ testReport.detectedLogLoss[i].uid = fieldValue++;
+ testReport.detectedLogLoss[i].pid = fieldValue++;
+ }
+ }
+
+ private static void populateEventQueueOverflowStatsTestData(
+ StatsLog.StatsdStatsReport testReport) {
+ testReport.queueOverflow = new StatsLog.StatsdStatsReport.EventQueueOverflow();
+ int fieldValue = 1;
+ testReport.queueOverflow.count = fieldValue++;
+ testReport.queueOverflow.minQueueHistoryNs = fieldValue++;
+ testReport.queueOverflow.maxQueueHistoryNs = fieldValue++;
+ }
+
+ @Override
+ public StatsLog.StatsdStatsReport getStatsdStatsReport() {
+ return testReport;
+ }
+ }
+
+ private static class TestEmptyStatsdHelper implements StatsdStatsHelper.IStatsdHelper {
+
+ StatsLog.StatsdStatsReport testReport = new StatsLog.StatsdStatsReport();
+
+ public TestEmptyStatsdHelper() {}
+
+ @Override
+ public StatsLog.StatsdStatsReport getStatsdStatsReport() {
+ return testReport;
+ }
+ }
+
+ private static void verifyAtomStats(Map<String, Long> result, int atomsCount) {
+ for (int i = 0; i < atomsCount; i++) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.ATOM_STATS_PREFIX,
+ String.valueOf(i + 1));
+ int fieldValue = i + 2;
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "count")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "error_count")),
+ Long.valueOf(fieldValue++));
+ }
+ }
+
+ private static void verifyConfigAlertStats(
+ Map<String, Long> result, String metricKeyPrefix, long count) {
+ for (long i = 0; i < count; i++) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ StatsdStatsHelper.ALERT_STATS_PREFIX,
+ String.valueOf(i + 1),
+ "alerted_times");
+ assertEquals(result.get(metricKey), Long.valueOf(i + 2));
+ }
+ }
+
+ private static void verifyConfigMatcherStats(
+ Map<String, Long> result, String metricKeyPrefix, long count) {
+ for (long i = 0; i < count; i++) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ StatsdStatsHelper.MATCHER_STATS_PREFIX,
+ String.valueOf(i + 1),
+ "matched_times");
+ assertEquals(result.get(metricKey), Long.valueOf(i + 2));
+ }
+ }
+
+ private static void verifyConfigConditionStats(
+ Map<String, Long> result, String metricKeyPrefix, long count) {
+ for (long i = 0; i < count; i++) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ StatsdStatsHelper.CONDITION_STATS_PREFIX,
+ String.valueOf(i + 1),
+ "max_tuple_counts");
+ assertEquals(result.get(metricKey), Long.valueOf(i + 2));
+ }
+ }
+
+ private static void verifyConfigMetricStats(
+ Map<String, Long> result, String metricKeyPrefix, long count) {
+ for (long i = 0; i < count; i++) {
+ final String metricKey =
+ MetricUtility.constructKey(
+ metricKeyPrefix,
+ StatsdStatsHelper.METRIC_STATS_PREFIX,
+ String.valueOf(i + 1),
+ "max_tuple_counts");
+ assertEquals(result.get(metricKey), Long.valueOf(i + 2));
+ }
+ }
+
+ private static void verifyConfigStats(
+ Map<String, Long> result,
+ int configStatsCount,
+ int configStatsMetricCount,
+ int configStatsConditionCount,
+ int configStatsMatcherCount,
+ int configStatsAlertCount) {
+
+ for (int i = 0; i < configStatsCount; i++) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.CONFIG_STATS_PREFIX,
+ String.valueOf(i + 1));
+
+ final String metricCountKey =
+ MetricUtility.constructKey(metricKeyPrefix, "metric_count");
+ assertEquals(result.get(metricCountKey), Long.valueOf(configStatsMetricCount));
+ verifyConfigMetricStats(result, metricKeyPrefix, result.get(metricCountKey));
+ final String conditionCountKey =
+ MetricUtility.constructKey(metricKeyPrefix, "condition_count");
+ assertEquals(result.get(conditionCountKey), Long.valueOf(configStatsConditionCount));
+ verifyConfigConditionStats(result, metricKeyPrefix, result.get(conditionCountKey));
+ final String matcherCountKey =
+ MetricUtility.constructKey(metricKeyPrefix, "matcher_count");
+ assertEquals(result.get(matcherCountKey), Long.valueOf(configStatsMatcherCount));
+ verifyConfigMatcherStats(result, metricKeyPrefix, result.get(matcherCountKey));
+ final String alertCountKey = MetricUtility.constructKey(metricKeyPrefix, "alert_count");
+ assertEquals(result.get(alertCountKey), Long.valueOf(configStatsAlertCount));
+ verifyConfigAlertStats(result, metricKeyPrefix, result.get(alertCountKey));
+ }
+ }
+
+ private static void verifyAnomalyAlarmStats(Map<String, Long> result) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.ANOMALY_ALARM_STATS_PREFIX);
+ final String metricKey = MetricUtility.constructKey(metricKeyPrefix, "alarms_registered");
+ assertEquals(result.get(metricKey), Long.valueOf(1));
+ }
+
+ private static void verifyPulledAtomStats(Map<String, Long> result, int pulledAtomStatsCount) {
+ for (int i = 0; i < pulledAtomStatsCount; i++) {
+ int fieldValue = i + 1;
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.PULLED_ATOM_STATS_PREFIX,
+ String.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "total_pull")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "total_pull_from_cache")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "min_pull_interval_sec")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "average_pull_time_nanos")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "max_pull_time_nanos")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "average_pull_delay_nanos")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "data_error")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "pull_timeout")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "pull_exceed_max_delay")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "pull_failed")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "empty_data")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "pull_registered_count")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "pull_unregistered_count")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "atom_error_count")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "binder_call_failed")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "failed_uid_provider_not_found")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "puller_not_found")),
+ Long.valueOf(fieldValue++));
+ }
+ }
+
+ private static void verifyAtomMetricStats(Map<String, Long> result, int atomMetricCount) {
+ for (int i = 0; i < atomMetricCount; i++) {
+ int fieldValue = i + 1;
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.ATOM_METRIC_STATS_PREFIX,
+ String.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "hard_dimension_limit_reached")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "late_log_event_skipped")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(metricKeyPrefix, "skipped_forward_buckets")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "bad_value_type")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "condition_change_in_next_bucket")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "invalidated_bucket")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "bucket_dropped")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "min_bucket_boundary_delay_ns")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "max_bucket_boundary_delay_ns")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(
+ MetricUtility.constructKey(
+ metricKeyPrefix, "bucket_unknown_condition")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "bucket_count")),
+ Long.valueOf(fieldValue++));
+ }
+ }
+
+ private static void verifyDetectedLogLossStats(
+ Map<String, Long> result, int logLossStatsCount) {
+ for (int i = 0; i < logLossStatsCount; i++) {
+ int fieldValue = i + 1;
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.DETECTED_LOG_LOSS_STATS_PREFIX,
+ String.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "count")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "last_error")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "last_tag")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "uid")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "pid")),
+ Long.valueOf(fieldValue++));
+ }
+ }
+
+ private static void verifyEventQueueOverfowStats(Map<String, Long> result) {
+ final String metricKeyPrefix =
+ MetricUtility.constructKey(
+ StatsdStatsHelper.STATSDSTATS_PREFIX,
+ StatsdStatsHelper.EVENT_QUEUE_OVERFLOW_STATS_PREFIX);
+
+ int fieldValue = 1;
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "count")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "min_queue_history_nanos")),
+ Long.valueOf(fieldValue++));
+ assertEquals(
+ result.get(MetricUtility.constructKey(metricKeyPrefix, "max_queue_history_nanos")),
+ Long.valueOf(fieldValue++));
+ }
+
+ @Test
+ public void testNonEmptyReport() throws Exception {
+ StatsdStatsHelper.IStatsdHelper statsdHelper = new TestNonEmptyStatsdHelper();
+ StatsdStatsHelper statsdStatsHelper = new StatsdStatsHelper(statsdHelper);
+
+ assertTrue(statsdStatsHelper.startCollecting());
+ final Map<String, Long> result = statsdStatsHelper.getMetrics();
+ verifyAtomStats(result, TestNonEmptyStatsdHelper.ATOM_STATS_COUNT);
+ verifyConfigStats(
+ result,
+ TestNonEmptyStatsdHelper.CONFIG_STATS_COUNT,
+ TestNonEmptyStatsdHelper.CONFIG_STATS_METRIC_COUNT,
+ TestNonEmptyStatsdHelper.CONFIG_STATS_CONDITION_COUNT,
+ TestNonEmptyStatsdHelper.CONFIG_STATS_MATCHER_COUNT,
+ TestNonEmptyStatsdHelper.CONFIG_STATS_ALERT_COUNT);
+ verifyAnomalyAlarmStats(result);
+ verifyPulledAtomStats(result, TestNonEmptyStatsdHelper.PULLED_ATOM_STATS_COUNT);
+ verifyAtomMetricStats(result, TestNonEmptyStatsdHelper.ATOM_METRIC_STATS_COUNT);
+ verifyDetectedLogLossStats(result, TestNonEmptyStatsdHelper.DETECTED_LOG_LOSS_STATS_COUNT);
+ verifyEventQueueOverfowStats(result);
+ assertTrue(statsdStatsHelper.stopCollecting());
+ }
+
+ @Test
+ public void testEmptyReport() throws Exception {
+ StatsdStatsHelper.IStatsdHelper statsdHelper = new TestEmptyStatsdHelper();
+ StatsdStatsHelper statsdStatsHelper = new StatsdStatsHelper(statsdHelper);
+
+ assertTrue(statsdStatsHelper.startCollecting());
+ final Map<String, Long> result = statsdStatsHelper.getMetrics();
+ assertEquals(result.size(), 0);
+ assertTrue(statsdStatsHelper.stopCollecting());
+ }
+}
diff --git a/libraries/collectors-helper/system/src/com/android/helpers/TimeInStateHelper.java b/libraries/collectors-helper/system/src/com/android/helpers/TimeInStateHelper.java
new file mode 100644
index 000000000..16be8c5f1
--- /dev/null
+++ b/libraries/collectors-helper/system/src/com/android/helpers/TimeInStateHelper.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers;
+
+import static com.android.helpers.MetricUtility.constructKey;
+
+import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** TimeInStateHelper is a helper to collect time_in_state frequency stats. */
+public class TimeInStateHelper implements ICollectorHelper<Long> {
+ private static final String LOG_TAG = TimeInStateHelper.class.getSimpleName();
+ private static final String METRIC_KEY_PREFIX = "time_in_state";
+ private static final String AVG_FREQ_KEY_SUFFIX = "avg_freq";
+ private static final Pattern TIME_IN_STATE_PATTERN = Pattern.compile("(\\d+)\\s+(\\d+)");
+ private static final String KEY_SOURCE_SEPARATOR = "@";
+
+ private static final String METRICS_LOG_FMT = "metrics key: %s, value: %d";
+ private static final String READ_FILE_CMD = "cat %s";
+ private static final String CHECK_FILE_EXIST_CMD = "file -b %s";
+
+ private List<String> mMetricKeys = new ArrayList<>();
+ private List<String> mSourceLocations = new ArrayList<>();
+ private List<Map<String, Long>> mBeforeFreqStats;
+ private List<Map<String, Long>> mAfterFreqStats;
+ private UiDevice mDevice;
+
+ public void setUp(String... freqKeys) {
+ if (freqKeys == null) {
+ return;
+ }
+ for (int i = 0; i < freqKeys.length; i++) {
+ String[] keys = freqKeys[i].split(KEY_SOURCE_SEPARATOR);
+ if (keys.length != 2) {
+ Log.e(LOG_TAG, "Failed to parse " + freqKeys[i]);
+ throw new RuntimeException("Failed to parse " + freqKeys[i]);
+ }
+ String key = keys[0].trim();
+ String source = keys[1].trim();
+ Log.i(LOG_TAG, "key: " + key + ", source: " + source);
+
+ String cmd = String.format(CHECK_FILE_EXIST_CMD, source);
+ String result = null;
+ try {
+ result = getDevice().executeShellCommand(cmd);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Error when checking source file " + source);
+ }
+ if (result == null || result.contains("No such file or directory")) {
+ Log.e(LOG_TAG, "Source " + source + " does not exist");
+ } else {
+ mMetricKeys.add(key);
+ mSourceLocations.add(source);
+ }
+ }
+ }
+
+ @Override
+ public boolean startCollecting() {
+ Log.i(LOG_TAG, "start collecting...");
+ try {
+ mBeforeFreqStats = readAllFreqStats();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to collect frequency stats at the start time.", e);
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean stopCollecting() {
+ Log.i(LOG_TAG, "stop collecting...");
+ return true;
+ }
+
+ protected UiDevice getDevice() {
+ if (mDevice == null) {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+ return mDevice;
+ }
+
+ @Override
+ public Map<String, Long> getMetrics() {
+ Log.i(LOG_TAG, "get metrics...");
+
+ try {
+ mAfterFreqStats = readAllFreqStats();
+ return calculateFreqStatsMetrics();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to collect frequency stats at the end time.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private List<Map<String, Long>> readAllFreqStats() {
+ List<Map<String, Long>> freqStatsMapList = new ArrayList<Map<String, Long>>();
+ for (String source : mSourceLocations) {
+ Map<String, Long> result = readFreqStats(source);
+ freqStatsMapList.add(result);
+ }
+ return freqStatsMapList;
+ }
+
+ private Map<String, Long> readFreqStats(String source) {
+ Map<String, Long> freqStatsMap = new HashMap<String, Long>();
+ String timeInStateData = "";
+ try {
+ String cmd = String.format(READ_FILE_CMD, source);
+ timeInStateData = getDevice().executeShellCommand(cmd);
+ Matcher m = TIME_IN_STATE_PATTERN.matcher(timeInStateData);
+ while (m.find()) {
+ String freq = m.group(1);
+ String time = m.group(2);
+ Log.i(LOG_TAG, source + ": freq= " + freq + ", time= " + time);
+
+ freqStatsMap.put(freq, Long.parseLong(time));
+ }
+ } catch (Exception e) {
+ Log.e(
+ LOG_TAG,
+ "Failed to read time_in_state from "
+ + source
+ + ", content:\n"
+ + timeInStateData,
+ e);
+ throw new RuntimeException(e);
+ }
+
+ if (freqStatsMap.isEmpty()) {
+ Log.e(
+ LOG_TAG,
+ "Can't parse time_in_state from " + source + ", content:\n" + timeInStateData);
+ throw new RuntimeException("Failed to collect freq metrics.");
+ }
+ return freqStatsMap;
+ }
+
+ private Map<String, Long> calculateFreqStatsMetrics() {
+ Log.i(LOG_TAG, "Collect frequency stats during the test");
+
+ Map<String, Long> freqStatsMetrics = new HashMap<String, Long>();
+ for (int i = 0; i < mSourceLocations.size(); i++) {
+ long totalWeightedFreq = 0;
+ long totalTime = 0;
+
+ Map<String, Long> afterFreqStatsMap = mAfterFreqStats.get(i);
+ Map<String, Long> beforeFreqStatsMap = mBeforeFreqStats.get(i);
+ String metricsPrefixKey = constructKey(METRIC_KEY_PREFIX, mMetricKeys.get(i));
+ for (Map.Entry<String, Long> entry : afterFreqStatsMap.entrySet()) {
+ String freq = entry.getKey();
+ long endTime = entry.getValue();
+ long startTime = beforeFreqStatsMap.getOrDefault(freq, 0L);
+ long runTime = endTime - startTime;
+ if (runTime == 0) {
+ continue;
+ }
+ long freqValue = Long.parseLong(freq);
+ totalWeightedFreq += runTime * freqValue;
+ totalTime += runTime;
+ String metricsKey = constructKey(metricsPrefixKey, freq);
+ freqStatsMetrics.put(metricsKey, runTime);
+ Log.i(LOG_TAG, String.format(METRICS_LOG_FMT, metricsKey, runTime));
+ }
+
+ if (totalTime > 0) {
+ String avgFreqMetricsKey = constructKey(metricsPrefixKey, AVG_FREQ_KEY_SUFFIX);
+ long avgFreq = totalWeightedFreq / totalTime;
+ freqStatsMetrics.put(avgFreqMetricsKey, avgFreq);
+ Log.i(LOG_TAG, String.format(METRICS_LOG_FMT, avgFreqMetricsKey, avgFreq));
+ }
+ }
+
+ return freqStatsMetrics;
+ }
+}
diff --git a/libraries/collectors-helper/system/test/src/com/android/helpers/tests/TimeInStateHelperTest.java b/libraries/collectors-helper/system/test/src/com/android/helpers/tests/TimeInStateHelperTest.java
new file mode 100644
index 000000000..9625b06ad
--- /dev/null
+++ b/libraries/collectors-helper/system/test/src/com/android/helpers/tests/TimeInStateHelperTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.helpers.TimeInStateHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Android unit test for {@link TimeInStateHelper}
+ *
+ * <p>To run: atest CollectorsHelperTest:com.android.helpers.tests.TimeInStateHelperTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TimeInStateHelperTest {
+ private static final String METRIC_KEY_PREFIX = "time_in_state";
+ private static final String AVG_FREQ_KEY_SUFFIX = "avg_freq";
+
+ private File file1;
+ private File file2;
+
+ private TimeInStateHelper mTimeInStateHelper;
+
+ private void writeToFile(File file, String content) {
+ try (FileWriter writer = new FileWriter(file.getPath())) {
+ writer.write(content);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private String constructKey(String key, String freq) {
+ return METRIC_KEY_PREFIX + "_" + key + "_" + freq;
+ }
+
+ @Before
+ public void setUp() {
+ mTimeInStateHelper = new TimeInStateHelper();
+
+ try {
+ file1 = File.createTempFile("temp1", "time_in_state");
+ file1.deleteOnExit();
+ file2 = File.createTempFile("temp2", "time_in_state");
+ file2.deleteOnExit();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void testCollectTimeInState_noSource() {
+ mTimeInStateHelper.startCollecting();
+ mTimeInStateHelper.stopCollecting();
+ Map<String, Long> results = mTimeInStateHelper.getMetrics();
+
+ assertEquals(results.size(), 0);
+ }
+
+ @Test
+ public void testCollectTimeInState_oneSource() {
+ String key1 = "key1";
+ mTimeInStateHelper.setUp(key1 + "@" + file1.getPath());
+
+ String content = "10000 100\n" + "250000 100\n";
+
+ writeToFile(file1, content);
+ mTimeInStateHelper.startCollecting();
+
+ content = "10000 200\n" + "250000 100\n" + "35000 100\n";
+ writeToFile(file1, content);
+ mTimeInStateHelper.stopCollecting();
+ Map<String, Long> results = mTimeInStateHelper.getMetrics();
+
+ assertEquals(results.size(), 3);
+ assertEquals(results.get(constructKey(key1, "10000")).longValue(), 100L);
+ assertEquals(results.get(constructKey(key1, "35000")).longValue(), 100L);
+ assertEquals(results.get(constructKey(key1, AVG_FREQ_KEY_SUFFIX)).longValue(), 22500L);
+ }
+
+ @Test
+ public void testCollectTimeInState_multipleSources() {
+ String key1 = "key1";
+ String key2 = "key2";
+ mTimeInStateHelper.setUp(key1 + "@" + file1.getPath(), key2 + "@" + file2.getPath());
+
+ String content1 = "10000 200\n";
+ writeToFile(file1, content1);
+ String content2 = "250000 100\n" + "750000 100\n";
+ writeToFile(file2, content2);
+ mTimeInStateHelper.startCollecting();
+
+ content1 = "10000 250\n" + "150000 50";
+ writeToFile(file1, content1);
+ content2 = "250000 180\n" + "750000 120\n";
+ writeToFile(file2, content2);
+ mTimeInStateHelper.stopCollecting();
+ Map<String, Long> results = mTimeInStateHelper.getMetrics();
+
+ assertEquals(results.size(), 6);
+ assertEquals(results.get(constructKey(key1, "10000")).longValue(), 50L);
+ assertEquals(results.get(constructKey(key1, "150000")).longValue(), 50L);
+ assertEquals(results.get(constructKey(key1, AVG_FREQ_KEY_SUFFIX)).longValue(), 80000L);
+ assertEquals(results.get(constructKey(key2, "250000")).longValue(), 80L);
+ assertEquals(results.get(constructKey(key2, "750000")).longValue(), 20L);
+ assertEquals(results.get(constructKey(key2, AVG_FREQ_KEY_SUFFIX)).longValue(), 350000L);
+ }
+}
diff --git a/libraries/compatibility-common-util/OWNERS b/libraries/compatibility-common-util/OWNERS
index cb3f3b8f7..882e0ecfd 100644
--- a/libraries/compatibility-common-util/OWNERS
+++ b/libraries/compatibility-common-util/OWNERS
@@ -1,5 +1,4 @@
# Android EngProd Approvers
dshi@google.com
-fdeng@google.com
guangzhu@google.com
jdesprez@google.com
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/GasTest.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/GasTest.java
index 833040181..1b80bee21 100644
--- a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/GasTest.java
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/GasTest.java
@@ -30,8 +30,9 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface GasTest {
- // The GAS requirement ID the GasTest applies to.
+ // The GAS requirement ID('s) the GasTest applies to.
// Example: @GasTest(requirement = "G-0-000")
+ // Example: @GasTest(requirement = "G-0-000, G-0-001, G-0-002")
String requirement();
// The minimum GAS software requirement version the GasTest applies to.
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ITestResult.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ITestResult.java
index 33340e66f..45811d229 100644
--- a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ITestResult.java
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ITestResult.java
@@ -170,4 +170,13 @@ public interface ITestResult extends Comparable<ITestResult> {
* @param resultHistories The test result histories.
*/
void setTestResultHistories(List<TestResultHistory> resultHistories);
+
+ /**
+ * Set test screenshots metadata of test item. This method is for per-case screenshots in CTS
+ * Verifier. If this field is used for large test suites like CTS, it may cause performance
+ * issues in APFE. Thus please do not use this field in other test suites.
+ */
+ void setTestScreenshotsMetadata(TestScreenshotsMetadata screenshotsMetadata);
+
+ TestScreenshotsMetadata getTestScreenshotsMetadata();
}
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ResultHandler.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ResultHandler.java
index ffe16b4ea..2ed9a3202 100644
--- a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -53,7 +53,11 @@ import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* Handles conversion of results to/from files.
+ *
+ * @deprecated b/170495912 Please avoid any change in the schema which would force updates in
+ * classes that currently handle the XML generation for *TS.
*/
+@Deprecated
public class ResultHandler {
private static final String ENCODING = "UTF-8";
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ScreenshotsMetadataHandler.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ScreenshotsMetadataHandler.java
new file mode 100644
index 000000000..a5b0a4e84
--- /dev/null
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/ScreenshotsMetadataHandler.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Handles conversion of results to/from files.
+ */
+public class ScreenshotsMetadataHandler {
+
+ private static final String ENCODING = "UTF-8";
+ private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
+ private static final String NS = null;
+ public static final String SCREENSHOTS_METADATA_FILE_NAME = "screenshots_metadata.xml";
+
+ // XML constants
+ private static final String CASE_TAG = "TestCase";
+ private static final String MODULE_TAG = "Module";
+ private static final String NAME_ATTR = "name";
+ private static final String RESULT_TAG = "Result";
+ private static final String TEST_TAG = "Test";
+
+ /**
+ * @param result - result of a single Compatibility invocation
+ * @param resultDir - directory where to write the screenshots metadata file
+ * @return The screenshots metadata file created.
+ */
+ public static File writeResults(IInvocationResult result, File resultDir)
+ throws IOException, XmlPullParserException {
+ File screenshotsMetadataFile = new File(resultDir, SCREENSHOTS_METADATA_FILE_NAME);
+ OutputStream stream = new FileOutputStream(screenshotsMetadataFile);
+ XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
+ serializer.setOutput(stream, ENCODING);
+ serializer.startDocument(ENCODING, false);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.processingInstruction(
+ "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\"");
+ serializer.startTag(NS, RESULT_TAG);
+ // Results
+ for (IModuleResult module : result.getModules()) {
+ serializer.startTag(NS, MODULE_TAG);
+ serializer.attribute(NS, NAME_ATTR, module.getName());
+ for (ICaseResult cr : module.getResults()) {
+ serializer.startTag(NS, CASE_TAG);
+ serializer.attribute(NS, NAME_ATTR, cr.getName());
+ for (ITestResult r : cr.getResults()) {
+ TestStatus status = r.getResultStatus();
+ if (status == null) {
+ continue; // test was not executed, don't report
+ }
+ serializer.startTag(NS, TEST_TAG);
+ serializer.attribute(NS, NAME_ATTR, r.getName());
+
+ TestScreenshotsMetadata screenshotsMetadata = r.getTestScreenshotsMetadata();
+ if (screenshotsMetadata != null) {
+ TestScreenshotsMetadata.serialize(serializer, screenshotsMetadata);
+ }
+ serializer.endTag(NS, TEST_TAG);
+ }
+ serializer.endTag(NS, CASE_TAG);
+ }
+ serializer.endTag(NS, MODULE_TAG);
+ }
+ serializer.endDocument();
+ return screenshotsMetadataFile;
+ }
+}
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestResult.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestResult.java
index d2a9ca91a..13db332d4 100644
--- a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestResult.java
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestResult.java
@@ -34,6 +34,7 @@ public class TestResult implements ITestResult {
private boolean mIsRetry;
private boolean mSkipped;
private List<TestResultHistory> mTestResultHistories;
+ private TestScreenshotsMetadata mTestScreenshotsMetadata;
/**
* Create a {@link TestResult} for the given test name.
@@ -237,6 +238,7 @@ public class TestResult implements ITestResult {
mIsRetry = false;
mSkipped = false;
mTestResultHistories = null;
+ mTestScreenshotsMetadata = null;
}
/**
@@ -299,4 +301,14 @@ public class TestResult implements ITestResult {
public void setTestResultHistories(List<TestResultHistory> resultHistories) {
mTestResultHistories = resultHistories;
}
+
+ @Override
+ public void setTestScreenshotsMetadata(TestScreenshotsMetadata screenshotsMetadata) {
+ mTestScreenshotsMetadata = screenshotsMetadata;
+ }
+
+ @Override
+ public TestScreenshotsMetadata getTestScreenshotsMetadata() {
+ return mTestScreenshotsMetadata;
+ }
}
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestScreenshotsMetadata.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestScreenshotsMetadata.java
new file mode 100644
index 000000000..423e7ed5f
--- /dev/null
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/TestScreenshotsMetadata.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utility class to add test case screenshot metadata to the report. This class records per-case
+ * screenshot metadata for CTS Verifier. If this field is used for large test suites like CTS, it
+ * may cause performance issues in APFE. Thus please do not use this class in other test suites.
+ */
+public class TestScreenshotsMetadata implements Serializable {
+
+ // XML constants
+ private static final String SCREENSHOTS_TAG = "Screenshots";
+ private static final String SCREENSHOT_TAG = "Screenshot";
+ private static final String NAME_ATTR = "name";
+ private static final String DESCRIPTION_ATTR = "description";
+
+ private final String mTestName;
+ private final Set<TestScreenshotsMetadata.ScreenshotMetadata> mScreenshotMetadataSet;
+
+ /**
+ * Constructor of test screenshots metadata.
+ *
+ * @param screenshots a Set of ScreenshotMetadata.
+ */
+ public TestScreenshotsMetadata(String testName,
+ Set<TestScreenshotsMetadata.ScreenshotMetadata> screenshots) {
+ mTestName = testName;
+ mScreenshotMetadataSet = screenshots;
+ }
+
+ /** Get test name */
+ public String getTestName() {
+ return mTestName;
+ }
+
+ /** Get a set of ScreenshotMetadata. */
+ public Set<TestScreenshotsMetadata.ScreenshotMetadata> getScreenshotMetadataSet() {
+ return mScreenshotMetadataSet;
+ }
+
+ @Override
+ public String toString() {
+ ArrayList<String> arr = new ArrayList<>();
+ for (TestScreenshotsMetadata.ScreenshotMetadata e : mScreenshotMetadataSet) {
+ arr.add(e.toString());
+ }
+ return String.join(", ", arr);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TestScreenshotsMetadata)) {
+ return false;
+ }
+ TestScreenshotsMetadata that = (TestScreenshotsMetadata) o;
+ return Objects.equals(mTestName, that.mTestName)
+ && Objects.equals(mScreenshotMetadataSet, that.mScreenshotMetadataSet);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTestName, mScreenshotMetadataSet);
+ }
+
+ /**
+ * Serializes a given {@link TestScreenshotsMetadata} to XML.
+ *
+ * @param serializer given serializer.
+ * @param screenshotsMetadata test screenshots metadata.
+ */
+ public static void serialize(
+ XmlSerializer serializer, TestScreenshotsMetadata screenshotsMetadata)
+ throws IOException {
+ if (screenshotsMetadata == null) {
+ throw new IllegalArgumentException("Test screenshots metadata was null");
+ }
+
+ serializer.startTag(null, SCREENSHOTS_TAG);
+
+ for (TestScreenshotsMetadata.ScreenshotMetadata screenshotMetadata :
+ screenshotsMetadata.getScreenshotMetadataSet()) {
+ serializer.startTag(null, SCREENSHOT_TAG);
+ serializer.attribute(null,
+ NAME_ATTR, String.valueOf(screenshotMetadata.getScreenshotName()));
+ serializer.attribute(
+ null, DESCRIPTION_ATTR, String.valueOf(screenshotMetadata.getDescription()));
+ serializer.endTag(null, SCREENSHOT_TAG);
+ }
+ serializer.endTag(null, SCREENSHOTS_TAG);
+ }
+
+ /** Single screenshot information */
+ public static class ScreenshotMetadata implements Serializable {
+ String mScreenshotName;
+ String mDescription;
+
+ public void setDescription(String description) {
+ mDescription = description;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public void setScreenshotName(String screenshotName) {
+ mScreenshotName = screenshotName;
+ }
+
+ public String getScreenshotName() {
+ return mScreenshotName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ScreenshotMetadata)) {
+ return false;
+ }
+ TestScreenshotsMetadata.ScreenshotMetadata that =
+ (TestScreenshotsMetadata.ScreenshotMetadata) o;
+ return mScreenshotName.equals(that.mScreenshotName)
+ && mDescription.equals(that.mDescription);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mScreenshotName, mDescription);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + mScreenshotName + ", " + mDescription + "]";
+ }
+ }
+}
diff --git a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/VersionCodes.java b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/VersionCodes.java
index 8760d40dc..cdccb7945 100644
--- a/libraries/compatibility-common-util/src/com/android/compatibility/common/util/VersionCodes.java
+++ b/libraries/compatibility-common-util/src/com/android/compatibility/common/util/VersionCodes.java
@@ -49,5 +49,5 @@ public class VersionCodes {
public static final int Q = 29;
public static final int R = 30;
public static final int S = 31;
-
+ public static final int S_V2 = 32;
}
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/AppVersionListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/AppVersionListener.java
index 809c9bdf3..8b6c4c6af 100644
--- a/libraries/device-collectors/src/main/java/android/device/collectors/AppVersionListener.java
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/AppVersionListener.java
@@ -64,7 +64,8 @@ public class AppVersionListener extends BaseCollectionListener<Long> {
if (pkgNamesString == null) {
Log.w(TAG, "No package name provided. All packages will be collected");
mAppVersionHelper.setUp();
+ } else {
+ mAppVersionHelper.setUp(pkgNamesString.split(PKG_NAME_SEPARATOR));
}
- mAppVersionHelper.setUp(pkgNamesString.split(PKG_NAME_SEPARATOR));
}
}
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java
index fcd596354..9d4a1323f 100644
--- a/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java
@@ -101,6 +101,10 @@ public class BaseMetricListener extends InstrumentationRunListener {
private int mCollectIterationInterval = 1;
private int mSkipMetricUntilIteration = 0;
+ // Whether to report the results as instrumentation results. Used by metric collector rules,
+ // which do not have the information to invoke InstrumentationRunFinished() to report metrics.
+ private boolean mReportAsInstrumentationResults = false;
+
public BaseMetricListener() {
mIncludeFilters = new ArrayList<>();
mExcludeFilters = new ArrayList<>();
@@ -190,9 +194,13 @@ public class BaseMetricListener extends InstrumentationRunListener {
}
if (mTestData.hasMetrics()) {
// Only send the status progress if there are metrics
+ if (mReportAsInstrumentationResults) {
+ getInstrumentation().addResults(mTestData.createBundleFromMetrics());
+ } else {
SendToInstrumentation.sendBundle(getInstrumentation(),
mTestData.createBundleFromMetrics());
}
+ }
}
super.testFinished(description);
}
@@ -333,15 +341,18 @@ public class BaseMetricListener extends InstrumentationRunListener {
}
/**
- * Create a directory inside external storage, and empty it.
+ * Create a directory inside external storage, and optionally empty it.
*
* @param dir full path to the dir to be created.
+ * @param empty whether to empty the new dirctory.
* @return directory file created
*/
- public File createAndEmptyDirectory(String dir) {
+ public File createDirectory(String dir, boolean empty) {
File rootDir = Environment.getExternalStorageDirectory();
File destDir = new File(rootDir, dir);
- executeCommandBlocking("rm -rf " + destDir.getAbsolutePath());
+ if (empty) {
+ executeCommandBlocking("rm -rf " + destDir.getAbsolutePath());
+ }
if (!destDir.exists() && !destDir.mkdirs()) {
Log.e(getTag(), "Unable to create dir: " + destDir.getAbsolutePath());
return null;
@@ -350,6 +361,16 @@ public class BaseMetricListener extends InstrumentationRunListener {
}
/**
+ * Create a directory inside external storage, and empty it.
+ *
+ * @param dir full path to the dir to be created.
+ * @return directory file created
+ */
+ public File createAndEmptyDirectory(String dir) {
+ return createDirectory(dir, true);
+ }
+
+ /**
* Delete a directory and all the file inside.
*
* @param rootDir the {@link File} directory to delete.
@@ -368,6 +389,11 @@ public class BaseMetricListener extends InstrumentationRunListener {
}
}
+ /** Sets whether metrics should be reported directly to instrumentation results. */
+ public final void setReportAsInstrumentationResults(boolean enabled) {
+ mReportAsInstrumentationResults = enabled;
+ }
+
/**
* Returns the name of the current class to be used as a logging tag.
*/
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/LyricMemProfilerCollector.java b/libraries/device-collectors/src/main/java/android/device/collectors/LyricMemProfilerCollector.java
new file mode 100644
index 000000000..3205fa298
--- /dev/null
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/LyricMemProfilerCollector.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.device.collectors;
+
+import android.os.Bundle;
+import android.device.collectors.annotations.OptionClass;
+
+import com.android.helpers.LyricMemProfilerHelper;
+
+@OptionClass(alias = "lyric-mem-profiler-collector")
+public class LyricMemProfilerCollector extends BaseCollectionListener<Integer> {
+
+ private static final String TAG = LyricMemProfilerCollector.class.getSimpleName();
+ private static final String PROFILE_PERIOD_KEY = "profile-period-ms-enable";
+ private static final String PROFILE_PID_NAME = "profile-pid-name";
+ private LyricMemProfilerHelper mHelper = new LyricMemProfilerHelper();
+
+ public LyricMemProfilerCollector() {
+ createHelperInstance(mHelper);
+ }
+
+ /** Adds the options for total pss collector. */
+ @Override
+ public void setupAdditionalArgs() {
+ Bundle args = getArgsBundle();
+ String argString = args.getString(PROFILE_PERIOD_KEY);
+ if (argString != null) {
+ mHelper.setProfilePeriodMs(Integer.parseInt(argString));
+ }
+ argString = args.getString(PROFILE_PID_NAME);
+ if (argString != null) {
+ mHelper.setProfilePidName(argString);
+ }
+ }
+}
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/ScreenRecordCollector.java b/libraries/device-collectors/src/main/java/android/device/collectors/ScreenRecordCollector.java
index 76fc1f945..da9c98c71 100644
--- a/libraries/device-collectors/src/main/java/android/device/collectors/ScreenRecordCollector.java
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/ScreenRecordCollector.java
@@ -47,6 +47,10 @@ public class ScreenRecordCollector extends BaseMetricListener {
// * "low" is 1/8 the resolution.
// * Otherwise, use the resolution.
@VisibleForTesting static final String QUALITY_ARG = "video-quality";
+ // Option for whether to empty the output directory before collecting. Defaults to true. Setting
+ // to false is useful when multiple test classes need recordings and recordings are pulled at
+ // the end of the test run.
+ @VisibleForTesting static final String EMPTY_OUTPUT_DIR_ARG = "empty-output-dir";
// Maximum parts per test (each part is <= 3min).
@VisibleForTesting static final int MAX_RECORDING_PARTS = 5;
private static final long VIDEO_TAIL_BUFFER = 500;
@@ -59,6 +63,7 @@ public class ScreenRecordCollector extends BaseMetricListener {
private RecordingThread mCurrentThread;
private String mVideoDimensions;
+ private boolean mEmptyOutputDir;
// Tracks the test iterations to ensure that each failure gets unique filenames.
// Key: test description; value: number of iterations.
@@ -75,8 +80,15 @@ public class ScreenRecordCollector extends BaseMetricListener {
}
@Override
- public void onTestRunStart(DataRecord runData, Description description) {
- mDestDir = createAndEmptyDirectory(OUTPUT_DIR);
+ public void onSetUp() {
+ mDestDir = createDirectory(OUTPUT_DIR, mEmptyOutputDir);
+ }
+
+ @Override
+ public void setupAdditionalArgs() {
+ mEmptyOutputDir =
+ Boolean.parseBoolean(
+ getArgsBundle().getString(EMPTY_OUTPUT_DIR_ARG, String.valueOf(true)));
try {
long scaleDown = 1;
@@ -163,18 +175,24 @@ public class ScreenRecordCollector extends BaseMetricListener {
/** Returns the recording's name for part {@code part} of test {@code description}. */
private File getOutputFile(Description description, int part) {
- final String baseName =
- String.format("%s.%s", description.getClassName(), description.getMethodName());
- // Omit the iteration number for the first iteration.
+ StringBuilder builder = new StringBuilder(description.getClassName());
+ if (description.getMethodName() != null) {
+ builder.append(".");
+ builder.append(description.getMethodName());
+ }
int iteration = mTestIterations.get(description.getDisplayName());
- final String fileName =
- String.format(
- "%s-video%s.mp4",
- iteration == 1
- ? baseName
- : String.join("-", baseName, String.valueOf(iteration)),
- part == 1 ? "" : part);
- return Paths.get(mDestDir.getAbsolutePath(), fileName).toFile();
+ // Omit the iteration number for the first iteration.
+ if (iteration > 1) {
+ builder.append("-");
+ builder.append(iteration);
+ }
+ builder.append("-video");
+ // Omit the part number for the first part.
+ if (part > 1) {
+ builder.append(part);
+ }
+ builder.append(".mp4");
+ return Paths.get(mDestDir.getAbsolutePath(), builder.toString()).toFile();
}
/** Returns a buffer duration for the end of the video. */
diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/README.md b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/README.md
new file mode 100644
index 000000000..011e9e537
--- /dev/null
+++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/README.md
@@ -0,0 +1,4 @@
+# Droidfood Configs
+
+These configs are used to collect many WW metrics on droidfood devices.
+They are usefull for statsd regression analysis within CrystalBall and GreenDay environment.
diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/droidfood-run-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/droidfood-run-level.pb
new file mode 100644
index 000000000..752abdae7
--- /dev/null
+++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/droidfood/droidfood-run-level.pb
Binary files differ
diff --git a/libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/StatsdMetadataListener.java b/libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/StatsdMetadataListener.java
new file mode 100644
index 000000000..eaebce92b
--- /dev/null
+++ b/libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/StatsdMetadataListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.device.collectors;
+
+import android.device.collectors.annotations.OptionClass;
+
+import com.android.helpers.StatsdStatsHelper;
+
+/**
+ * A {@link StatsdMetadataListener} that captures statsd metadata during test method.
+ *
+ * <p>The StatsdMetadataListener with {@link StatsdStatsHelper} support collects various statsd
+ * metadata information. This information will be populated into metrics with a prefix
+ * "statsdstats_" The metrics are about statsd logged atoms statistics, matchers and atom pullers
+ * such as below but not limited to: - atom stats (count, errors) - config stats (metrics,
+ * conditions, matcher & alerts count, and many more) - pulled atoms statsd (total pull count, data
+ * error, pull timeouts, etc.) Full list of available metrics is defined in {@link
+ * StatsdStatsHelper}
+ *
+ * <p>Do NOT throw exception anywhere in this class. We don't want to halt the test when metrics
+ * collection fails.
+ */
+@OptionClass(alias = "statsdstats-collector")
+public class StatsdMetadataListener extends BaseCollectionListener<Long> {
+ public StatsdMetadataListener() {
+ createHelperInstance(new StatsdStatsHelper());
+ }
+}
diff --git a/libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/TimeInStateListener.java b/libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/TimeInStateListener.java
new file mode 100644
index 000000000..691160787
--- /dev/null
+++ b/libraries/device-collectors/src/main/platform-collectors/src/android/device/collectors/TimeInStateListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.device.collectors;
+
+import android.device.collectors.annotations.OptionClass;
+import android.os.Bundle;
+import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.helpers.TimeInStateHelper;
+
+/**
+ * A {@link TimeInStateListener} captures time_in_state frequency stats in the format of
+ * "[frequency] [time]" in each line which means that the total duration in the [frequency] state is
+ * equal to [time].
+ *
+ * <p>Options:
+ *
+ * <ul>
+ * <li>-e key-source-mapping [mapping] : a comma-separated list of mapping "[key]@[source]" to
+ * provide [key] as a keyword part of the metric key and [source] as the source location
+ * containing the time_in_state data.
+ * </ul>
+ *
+ * <p>Do NOT throw exception anywhere in this class. We don't want to halt the test when metrics
+ * collection fails.
+ */
+@OptionClass(alias = "time-in-state-listener")
+public class TimeInStateListener extends BaseCollectionListener<Long> {
+
+ private static final String TAG = TimeInStateListener.class.getSimpleName();
+ @VisibleForTesting static final String ARG_SEPARATOR = ",";
+ @VisibleForTesting static final String ARG_KEY = "key-source-mapping";
+
+ public TimeInStateListener() {
+ createHelperInstance(new TimeInStateHelper());
+ }
+
+ @VisibleForTesting
+ public TimeInStateListener(Bundle args, TimeInStateHelper helper) {
+ super(args, helper);
+ }
+
+ @Override
+ public void setupAdditionalArgs() {
+ Bundle args = getArgsBundle();
+ String keyArgString = args.getString(ARG_KEY);
+ if (keyArgString == null) {
+ Log.w(TAG, "No time_in_state provided. Nothing will be collected");
+ return;
+ }
+ ((TimeInStateHelper) mHelper).setUp(keyArgString.split(ARG_SEPARATOR));
+ }
+}
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/BaseMetricListenerInstrumentedTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/BaseMetricListenerInstrumentedTest.java
index 5e73d15ad..c7fe14502 100644
--- a/libraries/device-collectors/src/test/java/android/device/collectors/BaseMetricListenerInstrumentedTest.java
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/BaseMetricListenerInstrumentedTest.java
@@ -725,4 +725,34 @@ public class BaseMetricListenerInstrumentedTest {
assertEquals(RUN_END_VALUE, resultBundle.getString(RUN_END_KEY));
assertEquals(2, resultBundle.size());
}
+
+ /** Test that the report as instrumentation result option works. */
+ @MetricOption(group = "testGroup")
+ @Test
+ public void testReportAsInstrumentationResultsIfEnabled() throws Exception {
+ mListener.setReportAsInstrumentationResults(true);
+
+ Description runDescription = Description.createSuiteDescription("run");
+ mListener.testRunStarted(runDescription);
+ Description testDescription = Description.createTestDescription("class", "method");
+ mListener.testStarted(testDescription);
+ mListener.testFinished(testDescription);
+ mListener.testRunFinished(new Result());
+ // AJUR runner is then gonna call instrumentationRunFinished
+ Bundle resultBundle = new Bundle();
+ mListener.instrumentationRunFinished(System.out, resultBundle, new Result());
+
+ // Check that results are reported via Instrumentation.addResults().
+ ArgumentCaptor<Bundle> capture = ArgumentCaptor.forClass(Bundle.class);
+ Mockito.verify(mMockInstrumentation, Mockito.times(1)).addResults(capture.capture());
+ Bundle addedResult = capture.getValue();
+ assertTrue(addedResult.containsKey(TEST_END_KEY));
+ assertEquals(TEST_END_VALUE + "method", addedResult.getString(TEST_END_KEY));
+
+ // Rather than Instrumentation.sendStatus().
+ Mockito.verify(mMockInstrumentation, Mockito.never())
+ .sendStatus(
+ Mockito.eq(SendToInstrumentation.INST_STATUS_IN_PROGRESS),
+ Mockito.any(Bundle.class));
+ }
}
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/ScreenRecordCollectorTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/ScreenRecordCollectorTest.java
index ce1bfa55c..71162f473 100644
--- a/libraries/device-collectors/src/test/java/android/device/collectors/ScreenRecordCollectorTest.java
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/ScreenRecordCollectorTest.java
@@ -16,7 +16,10 @@
package android.device.collectors;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
@@ -95,7 +98,7 @@ public class ScreenRecordCollectorTest {
listener = spy(new ScreenRecordCollector());
}
listener.setInstrumentation(mInstrumentation);
- doReturn(mLogDir).when(listener).createAndEmptyDirectory(anyString());
+ doReturn(mLogDir).when(listener).createDirectory(anyString(), anyBoolean());
doReturn(0L).when(listener).getTailBuffer();
doReturn(mDevice).when(listener).getDevice();
doReturn("1234").when(mDevice).executeShellCommand(eq("pidof screenrecord"));
@@ -113,7 +116,7 @@ public class ScreenRecordCollectorTest {
// Verify output directories are created on test run start.
mListener.testRunStarted(mRunDesc);
- verify(mListener).createAndEmptyDirectory(ScreenRecordCollector.OUTPUT_DIR);
+ verify(mListener).createDirectory(ScreenRecordCollector.OUTPUT_DIR, true);
// Walk through a number of test cases to simulate behavior.
for (int i = 1; i <= NUM_TEST_CASE; i++) {
@@ -156,7 +159,14 @@ public class ScreenRecordCollectorTest {
int videoCount = 0;
for (Bundle bundle : capturedBundle) {
for (String key : bundle.keySet()) {
- if (key.contains("mp4")) videoCount++;
+ if (key.contains("mp4")) {
+ videoCount++;
+ assertTrue(key.contains(mTestDesc.getClassName()));
+ assertTrue(key.contains(mTestDesc.getMethodName()));
+ String fileName = bundle.getString(key);
+ assertTrue(fileName.contains(mTestDesc.getClassName()));
+ assertTrue(fileName.contains(mTestDesc.getMethodName()));
+ }
}
}
assertEquals(NUM_TEST_CASE * ScreenRecordCollector.MAX_RECORDING_PARTS, videoCount);
@@ -280,4 +290,58 @@ public class ScreenRecordCollectorTest {
verify(mDevice, atLeastOnce())
.executeShellCommand(not(matches("screenrecord .*video.mp4")));
}
+
+ /** Test that the empty-output-dir works. */
+ @Test
+ public void testEmptyrOutputDirOptionSetToFalse() throws Exception {
+ Bundle args = new Bundle();
+ args.putString(ScreenRecordCollector.EMPTY_OUTPUT_DIR_ARG, "false");
+ mListener = initListener(args);
+
+ // Verify output directories are created on test run start.
+ mListener.testRunStarted(mRunDesc);
+ verify(mListener).createDirectory(ScreenRecordCollector.OUTPUT_DIR, false);
+ }
+
+ /**
+ * Test that descriptions with null method names only result in class names in the video file
+ * names.
+ */
+ @Test
+ public void testNullMethodNameDoesNotAppearInVideoName() throws Exception {
+ mListener = initListener(null);
+
+ mListener.testRunStarted(mRunDesc);
+
+ // mRunDesc does not have a method name.
+ mListener.testStarted(mRunDesc);
+ // Delay verification by 100 ms to ensure the thread was started.
+ SystemClock.sleep(100);
+ mListener.testFinished(mRunDesc);
+ mListener.testRunFinished(new Result());
+
+ Bundle resultBundle = new Bundle();
+ mListener.instrumentationRunFinished(System.out, resultBundle, new Result());
+
+ ArgumentCaptor<Bundle> capture = ArgumentCaptor.forClass(Bundle.class);
+ Mockito.verify(mInstrumentation, times(1))
+ .sendStatus(
+ Mockito.eq(SendToInstrumentation.INST_STATUS_IN_PROGRESS),
+ capture.capture());
+ Bundle metrics = capture.getValue();
+ // Ensure that we have recordings, and none of them have "null" in their file name or metric
+ // key.
+ boolean hasRecordings = false;
+ for (String key : metrics.keySet()) {
+ if (key.startsWith(mListener.getTag())) {
+ hasRecordings = true;
+ assertTrue(key.contains(mRunDesc.getClassName()));
+ assertFalse(key.contains("null"));
+ String fileName = metrics.getString(key);
+ assertTrue(fileName.contains(mRunDesc.getClassName()));
+ assertFalse(fileName.contains("null"));
+ }
+ }
+ assertTrue(hasRecordings);
+ }
}
diff --git a/libraries/device-collectors/src/test/platform/android/device/collectors/TimeInStateListenerTest.java b/libraries/device-collectors/src/test/platform/android/device/collectors/TimeInStateListenerTest.java
new file mode 100644
index 000000000..7ab9e17ec
--- /dev/null
+++ b/libraries/device-collectors/src/test/platform/android/device/collectors/TimeInStateListenerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.device.collectors;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.helpers.TimeInStateHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Android Unit tests for {@link TimeInStateListener}.
+ *
+ * <p>To run: atest CollectorDeviceLibPlatformTest:android.device.collectors.TimeInStateListenerTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TimeInStateListenerTest {
+
+ @Mock private TimeInStateHelper mTimeInStateHelper;
+ @Mock private Instrumentation mInstrumentation;
+
+ private Description mRunDesc;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ mRunDesc = Description.createSuiteDescription("run");
+ }
+
+ @Test
+ public void testListener_noSource() throws Exception {
+ TimeInStateListener listener = initListener(new Bundle(), mTimeInStateHelper);
+ listener.testRunStarted(mRunDesc);
+ verify(mTimeInStateHelper, never()).setUp(any());
+ }
+
+ @Test
+ public void testListener_withSources() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putString(
+ TimeInStateListener.ARG_KEY,
+ String.join(TimeInStateListener.ARG_SEPARATOR, "key1@temp1", "key2@temp2"));
+ TimeInStateListener listener = initListener(bundle, mTimeInStateHelper);
+ listener.testRunStarted(mRunDesc);
+ verify(mTimeInStateHelper).setUp("key1@temp1", "key2@temp2");
+ }
+
+ private TimeInStateListener initListener(Bundle bundle, TimeInStateHelper helper) {
+ TimeInStateListener listener = new TimeInStateListener(bundle, helper);
+ listener.setInstrumentation(mInstrumentation);
+ return listener;
+ }
+}
diff --git a/libraries/flicker/.gitignore b/libraries/flicker/.gitignore
new file mode 100644
index 000000000..caa32e675
--- /dev/null
+++ b/libraries/flicker/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+*.iml \ No newline at end of file
diff --git a/libraries/flicker/Android.bp b/libraries/flicker/Android.bp
index 13484795e..f65c6de09 100644
--- a/libraries/flicker/Android.bp
+++ b/libraries/flicker/Android.bp
@@ -21,6 +21,9 @@ package {
java_test {
name: "flickerlib",
platform_apis: true,
+ optimize: {
+ enabled: false
+ },
srcs: [
"src/**/*.java",
"src/**/*.kt"
@@ -34,14 +37,21 @@ java_test {
java_library {
name: "flickerlib-core",
platform_apis: true,
+ optimize: {
+ enabled: false
+ },
srcs: [
"src/com/android/server/wm/flicker/**/*.java",
"src/com/android/server/wm/flicker/**/*.kt"
],
+ java_resource_dirs: [
+ "src/com/android/server/wm/flicker/service/resources/"
+ ],
exclude_srcs: [
"**/helpers/*",
],
static_libs: [
+ "flickerlib-helpers",
"compatibility-device-util-axt",
"ub-uiautomator",
"androidx.test.uiautomator_uiautomator",
@@ -51,12 +61,16 @@ java_library {
"wm-proto-parsers",
"platform-test-annotations",
"platform-test-core-rules",
+ "health-testing-utils",
],
}
java_library {
name: "flickerlib-helpers",
sdk_version: "test_current",
+ optimize: {
+ enabled: false
+ },
srcs: [
"src/**/helpers/*.java",
"src/**/helpers/*.kt",
@@ -72,6 +86,9 @@ java_library {
java_library {
name: "wm-proto-parsers",
sdk_version: "test_current",
+ optimize: {
+ enabled: false
+ },
srcs: [
"src/com/android/server/wm/traces/**/*.java",
"src/com/android/server/wm/traces/**/*.kt",
@@ -81,5 +98,16 @@ java_library {
"androidx.test.ext.junit",
"platformprotosnano",
"layersprotosnano",
+ "flicker-tags-proto",
+ ],
+}
+
+java_library {
+ name: "flicker-tags-proto",
+ srcs: [
+ "**/*.proto",
],
+ optimize: {
+ enabled: false
+ }
}
diff --git a/libraries/flicker/README.md b/libraries/flicker/README.md
index 97f79b991..0b5842433 100644
--- a/libraries/flicker/README.md
+++ b/libraries/flicker/README.md
@@ -111,7 +111,7 @@ Example of a failed test:
## Running Tests
-The tests can be run as any other Android JUnit tests. `frameworks/base/tests/FlickerTests` uses the library to test common UI transitions. Run `atest FlickerTest` to execute these tests.
+The tests can be run as any other Android JUnit tests. `frameworks/base/tests/FlickerTests` uses the library to test common UI transitions. Run `atest FlickerTests` to execute these tests.
---
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/Flicker.kt b/libraries/flicker/src/com/android/server/wm/flicker/Flicker.kt
index 7bb2074d7..cc174d9ed 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/Flicker.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/Flicker.kt
@@ -137,11 +137,9 @@ class Flicker(
val result = result
requireNotNull(result)
- val failures = result.checkAssertions(listOf(assertion))
- val failureMessage = failures.joinToString("\n") { it.message }
-
- if (failureMessage.isNotEmpty()) {
- throw AssertionError(failureMessage)
+ val failures = result.checkAssertion(assertion)
+ if (failures.isNotEmpty()) {
+ throw failures.first()
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerBlockJUnit4ClassRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerBlockJUnit4ClassRunner.kt
index b43eab93e..d7f7fafc7 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerBlockJUnit4ClassRunner.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/FlickerBlockJUnit4ClassRunner.kt
@@ -16,7 +16,9 @@
package com.android.server.wm.flicker
+import android.platform.test.util.TestFilter
import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.internal.runners.statements.RunAfters
import org.junit.runner.notification.RunNotifier
@@ -30,6 +32,16 @@ import java.lang.reflect.Modifier
* Implements the JUnit 4 standard test case class model, parsing from a flicker DSL.
*
* Supports both assertions in {@link org.junit.Test} and assertions defined in the DSL
+ *
+ * When using this runnr the default `atest class#method` command doesn't work.
+ * Instead use: -- --test-arg \
+ * com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests:=<TEST_NAME>
+ *
+ * For example:
+ * `atest FlickerTests -- \
+ * --test-arg com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests\
+ * :=com.android.server.wm.flicker.close.\
+ * CloseAppBackButtonTest#launcherWindowBecomesVisible[ROTATION_90_GESTURAL_NAV]`
*/
class FlickerBlockJUnit4ClassRunner @JvmOverloads constructor(
test: TestWithParameters,
@@ -42,6 +54,18 @@ class FlickerBlockJUnit4ClassRunner @JvmOverloads constructor(
/**
* {@inheritDoc}
*/
+ override fun getChildren(): MutableList<FrameworkMethod> {
+ val arguments = InstrumentationRegistry.getArguments()
+ val validChildren = super.getChildren().filter {
+ val childDescription = describeChild(it)
+ TestFilter.isFilteredOrUnspecified(arguments, childDescription)
+ }
+ return validChildren.toMutableList()
+ }
+
+ /**
+ * {@inheritDoc}
+ */
override fun classBlock(notifier: RunNotifier): Statement {
val statement = childrenInvoker(notifier)
val cleanUpMethod = getFlickerCleanUpMethod()
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerResult.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerResult.kt
index 006a153f6..62da20d53 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerResult.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/FlickerResult.kt
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker
import com.android.server.wm.flicker.assertions.AssertionData
import com.android.server.wm.flicker.assertions.FlickerAssertionError
+import com.android.server.wm.flicker.assertions.FlickerAssertionErrorBuilder
import com.google.common.truth.Truth
/**
@@ -51,22 +52,23 @@ data class FlickerResult(
}
/**
- * Run the assertions on the trace
+ * Run the assertion on the trace
*
- * @throws AssertionError If the assertions fail or the transition crashed
+ * @throws AssertionError If the assertion fail or the transition crashed
*/
- internal fun checkAssertions(
- assertions: List<AssertionData>
- ): List<FlickerAssertionError> {
+ internal fun checkAssertion(assertion: AssertionData): List<FlickerAssertionError> {
checkIsExecuted()
- val currFailures: List<FlickerAssertionError> = runs.flatMap { run ->
- assertions.filter { it.tag == run.assertionTag }.mapNotNull { assertion ->
- try {
- assertion.checkAssertion(run)
- null
- } catch (error: Throwable) {
- FlickerAssertionError(error, assertion, run)
- }
+ val filteredRuns = runs.filter { it.assertionTag == assertion.tag }
+ val currFailures = filteredRuns.mapNotNull { run ->
+ try {
+ assertion.checkAssertion(run)
+ null
+ } catch (error: Throwable) {
+ FlickerAssertionErrorBuilder()
+ .fromError(error)
+ .atTag(assertion.tag)
+ .withTrace(run.traceFiles)
+ .build()
}
}
failures.addAll(currFailures)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerRunResult.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerRunResult.kt
index 5508a53a7..22de5b998 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerRunResult.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/FlickerRunResult.kt
@@ -54,11 +54,11 @@ class FlickerRunResult private constructor(
/**
* Truth subject that corresponds to a [WindowManagerTrace] or [WindowManagerState]
*/
- private val wmSubject: FlickerSubject?,
+ internal val wmSubject: FlickerSubject?,
/**
* Truth subject that corresponds to a [LayersTrace] or [LayerTraceEntry]
*/
- private val layersSubject: FlickerSubject?,
+ internal val layersSubject: FlickerSubject?,
/**
* Truth subject that corresponds to a list of [FocusEvent]
*/
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt
index 3f63df7d8..dc10c0003 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt
@@ -18,7 +18,9 @@ package com.android.server.wm.flicker
import android.util.Log
import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.traces.parser.DeviceStateDump
+import com.android.server.wm.traces.common.ConditionList
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.parser.DeviceDumpParser
import com.android.server.wm.traces.parser.getCurrentState
import java.io.IOException
import java.nio.file.Files
@@ -81,6 +83,10 @@ open class TransitionRunner {
* @param flicker test specification
*/
internal open fun run(flicker: Flicker): FlickerResult {
+ val uiStableCondition = ConditionList(listOf(
+ WindowManagerConditionsFactory.isWMStateComplete(),
+ WindowManagerConditionsFactory.hasLayersAnimating().negate()
+ ))
val runs = mutableListOf<FlickerRunResult>()
var executionError: Throwable? = null
try {
@@ -89,10 +95,12 @@ open class TransitionRunner {
for (iteration in 0 until flicker.repetitions) {
try {
flicker.runSetup.forEach { it.invoke(flicker) }
+ flicker.wmHelper.waitFor(uiStableCondition)
flicker.traceMonitors.forEach { it.start() }
flicker.frameStatsMonitor?.run { start() }
flicker.transitions.forEach { it.invoke(flicker) }
} finally {
+ flicker.wmHelper.waitFor(uiStableCondition)
flicker.traceMonitors.forEach { it.tryStop() }
flicker.frameStatsMonitor?.run { tryStop() }
flicker.runTeardown.forEach { it.invoke(flicker) }
@@ -160,7 +168,7 @@ open class TransitionRunner {
tags.add(tag)
val deviceStateBytes = getCurrentState(flicker.instrumentation.uiAutomation)
- val deviceState = DeviceStateDump.fromDump(deviceStateBytes.first, deviceStateBytes.second)
+ val deviceState = DeviceDumpParser.fromDump(deviceStateBytes.first, deviceStateBytes.second)
try {
val wmTraceFile = flicker.outputDir.resolve(
getTaggedFilePath(flicker, tag, "wm_trace"))
@@ -176,8 +184,8 @@ open class TransitionRunner {
val result = builder.buildStateResult(
tag,
- deviceState.wmTrace,
- deviceState.layersTrace
+ deviceState.wmState?.asTrace(),
+ deviceState.layerState?.asTrace()
)
tagsResults.add(result)
} catch (e: IOException) {
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunnerWithRules.kt b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunnerWithRules.kt
index fff00c558..9919af5f1 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunnerWithRules.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunnerWithRules.kt
@@ -19,7 +19,9 @@ package com.android.server.wm.flicker
import android.platform.test.rule.NavigationModeRule
import android.platform.test.rule.PressHomeRule
import android.platform.test.rule.UnlockScreenRule
+import com.android.server.wm.flicker.helpers.SampleAppHelper
import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.flicker.rules.LaunchAppRule
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import org.junit.rules.RuleChain
import org.junit.runner.Description
@@ -33,12 +35,26 @@ import org.junit.runners.model.Statement
class TransitionRunnerWithRules(private val testConfig: Map<String, Any?>) : TransitionRunner() {
private var result: FlickerResult? = null
- private fun buildDefaultSetupRules(): RuleChain {
- return RuleChain.outerRule(ChangeDisplayOrientationRule(testConfig.startRotation))
- .around(RemoveAllTasksButHomeRule())
+ /**
+ * Create the default flicker test setup rules. In order:
+ * - unlock device
+ * - change orientation
+ * - change navigation mode
+ * - launch an app
+ * - remove all apps
+ * - go to home screen
+ *
+ * (b/186740751) An app should be launched because, after changing the navigation mode,
+ * the first app launch is handled as a screen size change (similar to a rotation), this
+ * causes different problems during testing (e.g. IME now shown on app launch)
+ */
+ private fun buildDefaultSetupRules(flicker: Flicker): RuleChain {
+ return RuleChain.outerRule(UnlockScreenRule())
.around(NavigationModeRule(testConfig.navBarMode))
+ .around(LaunchAppRule(SampleAppHelper(flicker.instrumentation)))
+ .around(RemoveAllTasksButHomeRule())
+ .around(ChangeDisplayOrientationRule(testConfig.startRotation))
.around(PressHomeRule())
- .around(UnlockScreenRule())
}
private fun buildTransitionRule(flicker: Flicker): Statement {
@@ -54,7 +70,7 @@ class TransitionRunnerWithRules(private val testConfig: Map<String, Any?>) : Tra
}
private fun buildTransitionChain(flicker: Flicker): Statement {
- val setupRules = buildDefaultSetupRules()
+ val setupRules = buildDefaultSetupRules(flicker)
val transitionRule = buildTransitionRule(flicker)
return setupRules.apply(transitionRule, Description.EMPTY)
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group2.kt b/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group2.kt
index 79f629811..5ac3a126a 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group2.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group2.kt
@@ -18,7 +18,6 @@ package com.android.server.wm.flicker.annotation
/**
* The group annotations enable to run tests in parallel according to the arguments of test runner.
- * By default, the test without group annotation are considered to be in this group.
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group3.kt b/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group3.kt
index e67fe0762..98c4676f3 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group3.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group3.kt
@@ -18,7 +18,6 @@ package com.android.server.wm.flicker.annotation
/**
* The group annotations enable to run tests in parallel according to the arguments of test runner.
- * By default, the test without group annotation are considered to be in this group.
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group4.kt b/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group4.kt
new file mode 100644
index 000000000..c1cdcfc4b
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/annotation/Group4.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.annotation
+
+/**
+ * The group annotations enable to run tests in parallel according to the arguments of test runner.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+annotation class Group4 \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionData.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionData.kt
index c3847601f..cbb4f1117 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionData.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionData.kt
@@ -16,13 +16,14 @@
package com.android.server.wm.flicker.assertions
+import androidx.annotation.VisibleForTesting
import com.android.server.wm.flicker.FlickerRunResult
import kotlin.reflect.KClass
/**
* Class containing basic data about a trace assertion for Flicker DSL
*/
-data class AssertionData internal constructor(
+data class AssertionData @VisibleForTesting constructor(
/**
* Segment of the trace where the assertion will be applied (e.g., start, end).
*/
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/Assertions.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/Assertions.kt
index 26e4404fb..5f1471a9c 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/Assertions.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/assertions/Assertions.kt
@@ -26,39 +26,61 @@ typealias Assertion<T> = (T) -> Unit
/**
* Utility class to store assertions with an identifier to help generate more useful debug data
* when dealing with multiple assertions.
+ *
+ * @param assertion Assertion to execute
+ * @param name Assertion name
+ * @param isOptional If the assertion is optional (can fail) or not (must pass)
*/
open class NamedAssertion<T> (
private val assertion: Assertion<T>,
- open val name: String
+ open val name: String,
+ open val isOptional: Boolean = false
) : Assertion<T> {
override fun invoke(target: T): Unit = assertion.invoke(target)
- override fun toString(): String = "Assertion($name)"
+ override fun toString(): String = "Assertion($name)${if (isOptional) "[optional]" else ""}"
}
/**
* Utility class to store assertions composed of multiple individual assertions
*/
-class CompoundAssertion<T>(assertion: Assertion<T>, name: String) :
+class CompoundAssertion<T>(assertion: Assertion<T>, name: String, optional: Boolean) :
NamedAssertion<T>(assertion, name) {
private val assertions = mutableListOf<NamedAssertion<T>>()
init {
- add(assertion, name)
+ add(assertion, name, optional)
}
+ override val isOptional: Boolean
+ get() = assertions.all { it.isOptional }
+
override val name: String
get() = assertions.joinToString(" and ") { it.name }
/**
* Executes all [assertions] on [target]
+ *
+ * In case of failure, returns the first non-optional failure (if available)
+ * or the first failed assertion
*/
override fun invoke(target: T) {
- val failure = assertions.mapNotNull {
- kotlin.runCatching { it.invoke(target) }.exceptionOrNull()
- }.firstOrNull()
- if (failure != null) {
- throw failure
+ val failures = assertions
+ .mapNotNull { assertion ->
+ val error = kotlin.runCatching { assertion.invoke(target) }.exceptionOrNull()
+ if (error != null) {
+ Pair(assertion, error)
+ } else {
+ null
+ }
+ }
+ val nonOptionalFailure = failures.firstOrNull { !it.first.isOptional }
+ if (nonOptionalFailure != null) {
+ throw nonOptionalFailure.second
+ }
+ val firstFailure = failures.firstOrNull()
+ if (firstFailure != null) {
+ throw firstFailure.second
}
}
@@ -67,7 +89,7 @@ class CompoundAssertion<T>(assertion: Assertion<T>, name: String) :
/**
* Adds a new assertion to the list
*/
- fun add(assertion: Assertion<T>, name: String) {
- assertions.add(NamedAssertion(assertion, name))
+ fun add(assertion: Assertion<T>, name: String, optional: Boolean) {
+ assertions.add(NamedAssertion(assertion, name, optional))
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionsChecker.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionsChecker.kt
index d3fb57945..a714edcfe 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionsChecker.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionsChecker.kt
@@ -16,6 +16,8 @@
package com.android.server.wm.flicker.assertions
+import android.util.Log
+import com.android.server.wm.flicker.FLICKER_TAG
import com.google.common.truth.Fact
import kotlin.math.max
@@ -31,15 +33,15 @@ class AssertionsChecker<T : FlickerSubject> {
private val assertions = mutableListOf<CompoundAssertion<T>>()
private var skipUntilFirstAssertion = false
- fun add(name: String, assertion: Assertion<T>) {
- assertions.add(CompoundAssertion(assertion, name))
+ fun add(name: String, isOptional: Boolean = false, assertion: Assertion<T>) {
+ assertions.add(CompoundAssertion(assertion, name, isOptional))
}
/**
* Append [assertion] to the last existing set of assertions.
*/
- fun append(name: String, assertion: Assertion<T>) {
- assertions.last().add(assertion, name)
+ fun append(name: String, isOptional: Boolean = false, assertion: Assertion<T>) {
+ assertions.last().add(assertion, name, isOptional)
}
/**
@@ -73,19 +75,31 @@ class AssertionsChecker<T : FlickerSubject> {
var entryIndex = 0
var assertionIndex = 0
var lastPassedAssertionIndex = -1
+ val assertionTrace = mutableListOf<String>()
while (assertionIndex < assertions.size && entryIndex < entries.size) {
val currentAssertion = assertions[assertionIndex]
val currEntry = entries[entryIndex]
try {
+ val log = "${assertionIndex + 1}/${assertions.size}:[${currentAssertion.name}]\t" +
+ "Entry: ${entryIndex + 1}/${entries.size} $currEntry"
+ Log.v(FLICKER_TAG, "Checking Assertion: $log")
+ assertionTrace.add(log)
currentAssertion.invoke(currEntry)
lastPassedAssertionIndex = assertionIndex
entryIndex++
} catch (e: Throwable) {
+ // ignore errors are the start of the trace
val ignoreFailure = skipUntilFirstAssertion && lastPassedAssertionIndex == -1
if (ignoreFailure) {
entryIndex++
continue
}
+ // failure is an optional assertion, just consider it passed skip it
+ if (currentAssertion.isOptional) {
+ lastPassedAssertionIndex = assertionIndex
+ assertionIndex++
+ continue
+ }
if (lastPassedAssertionIndex != assertionIndex) {
val prevEntry = entries[max(entryIndex - 1, 0)]
prevEntry.fail(e)
@@ -97,17 +111,23 @@ class AssertionsChecker<T : FlickerSubject> {
}
}
}
+ // Didn't pass any assertions
if (lastPassedAssertionIndex == -1 && assertions.isNotEmpty() && failures.isEmpty()) {
entries.first().fail("Assertion never passed", assertions.first())
}
- if (failures.isEmpty() && assertionIndex != assertions.lastIndex) {
- val reason = listOf(
- Fact.fact("Assertion never became false", assertions[assertionIndex]),
- Fact.fact("Passed assertions", assertions.take(assertionIndex).joinToString(",")),
- Fact.fact("Untested assertions",
- assertions.drop(assertionIndex + 1).joinToString(","))
- )
+ val untestedAssertions = assertions.drop(assertionIndex + 1)
+ if (failures.isEmpty() && untestedAssertions.any { !it.isOptional }) {
+ val passedAssertionsFacts = assertions.take(assertionIndex)
+ .map { Fact.fact("Passed", it) }
+ val untestedAssertionsFacts = untestedAssertions
+ .map { Fact.fact("Untested", it) }
+ val trace = assertionTrace.map { Fact.fact("Trace", it) }
+ val reason = mutableListOf<Fact>()
+ reason.addAll(passedAssertionsFacts)
+ reason.add(Fact.fact("Assertion never failed", assertions[assertionIndex]))
+ reason.addAll(untestedAssertionsFacts)
+ reason.addAll(trace)
entries.first().fail(reason)
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionError.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionError.kt
index 9061a6ae8..7947a6fba 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionError.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionError.kt
@@ -16,47 +16,11 @@
package com.android.server.wm.flicker.assertions
-import com.android.server.wm.flicker.FlickerRunResult
-import com.android.server.wm.flicker.traces.FlickerSubjectException
import java.nio.file.Path
import kotlin.AssertionError
class FlickerAssertionError(
- cause: Throwable,
- @JvmField val assertion: AssertionData,
- @JvmField val iteration: Int,
- @JvmField val assertionTag: String,
- @JvmField val traceFiles: List<Path>
-) : AssertionError(cause) {
- constructor(cause: Throwable, assertion: AssertionData, run: FlickerRunResult)
- : this(cause, assertion, run.iteration, run.assertionTag, run.traceFiles)
-
- override val message: String
- get() = buildString {
- append("\n")
- append("Test failed")
- append("\n")
- append("Iteration: ")
- append(iteration)
- append("\n")
- append("Tag: ")
- append(assertionTag)
- append("\n")
- append("Files: ")
- append("\n")
- traceFiles.forEach {
- append("\t")
- append(it)
- append("\n")
- }
- // For subject exceptions, add the facts (layer/window/entry/etc)
- // and the original cause of failure
- if (cause is FlickerSubjectException) {
- append(cause.facts)
- append("\n")
- cause.cause?.message?.let { append(it) }
- } else {
- cause?.message?.let { append(it) }
- }
- }
-} \ No newline at end of file
+ message: String,
+ cause: Throwable?,
+ val traceFiles: List<Path>
+) : AssertionError(message, cause)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt
new file mode 100644
index 000000000..74f32da84
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.assertions
+
+import com.android.server.wm.flicker.dsl.AssertionTag
+import com.android.server.wm.flicker.traces.FlickerSubjectException
+import com.google.common.truth.Fact
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import java.nio.file.Path
+
+class FlickerAssertionErrorBuilder {
+ private var error: Throwable? = null
+ private var traceFiles: List<Path> = emptyList()
+ private var tag = ""
+
+ fun fromError(error: Throwable): FlickerAssertionErrorBuilder = apply {
+ this.error = error
+ }
+
+ fun withTrace(traceFiles: List<Path>): FlickerAssertionErrorBuilder = apply {
+ this.traceFiles = traceFiles
+ }
+
+ fun atTag(tag: String): FlickerAssertionErrorBuilder = apply {
+ this.tag = when (tag) {
+ AssertionTag.START -> "before transition (initial state)"
+ AssertionTag.END -> "after transition (final state)"
+ AssertionTag.ALL -> "during transition"
+ else -> "at user-defined location ($tag)"
+ }
+ }
+
+ fun build(): FlickerAssertionError {
+ return FlickerAssertionError(errorMessage, rootCause, traceFiles)
+ }
+
+ private val errorMessage get() = buildString {
+ val error = error
+ requireNotNull(error)
+ if (error is FlickerSubjectException) {
+ appendln(error.errorType)
+ appendln()
+ append(error.errorDescription)
+ appendln()
+ append(error.subjectInformation)
+ append("\t").appendln(Fact.fact("Location", tag))
+ appendln()
+ } else {
+ appendln(error.message)
+ }
+ appendln("Trace files:")
+ append(traceFileMessage)
+ appendln()
+ appendln("Cause:")
+ append(rootCauseStackTrace)
+ appendln()
+ appendln("Full stacktrace:")
+ appendln()
+ }
+
+ private val traceFileMessage get() = buildString {
+ traceFiles.forEach {
+ append("\t")
+ appendln(it)
+ }
+ }
+
+ private val rootCauseStackTrace: String get() {
+ val rootCause = rootCause
+ return if (rootCause != null) {
+ val baos = ByteArrayOutputStream()
+ PrintStream(baos, true)
+ .use { ps -> rootCause.printStackTrace(ps) }
+ "\t$baos"
+ } else {
+ ""
+ }
+ }
+
+ /**
+ * In some paths the exceptions are encapsulated by the Truth subjects
+ * To make sure the correct error is printed, located the first non-subject
+ * related exception and use that as cause.
+ */
+ private val rootCause: Throwable? get() {
+ var childCause: Throwable? = this.error?.cause
+ if (childCause != null && childCause is FlickerSubjectException) {
+ childCause = childCause.cause
+ }
+ return childCause
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerSubject.kt
index d2f956fb2..c54bc9d70 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerSubject.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.assertions
+import androidx.annotation.VisibleForTesting
import com.android.server.wm.flicker.traces.FlickerSubjectException
import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
@@ -29,8 +30,20 @@ abstract class FlickerSubject(
protected val fm: FailureMetadata,
data: Any?
) : Subject(fm, data) {
- abstract val defaultFacts: String
abstract fun clone(): FlickerSubject
+ @VisibleForTesting
+ abstract val timestamp: Long
+ protected abstract val parent: FlickerSubject?
+
+ protected abstract val selfFacts: List<Fact>
+ val completeFacts: List<Fact> get() {
+ val facts = selfFacts.toMutableList()
+ parent?.run {
+ val ancestorFacts = this.completeFacts
+ facts.addAll(ancestorFacts)
+ }
+ return facts
+ }
/**
* Fails an assertion on a subject
@@ -95,4 +108,10 @@ abstract class FlickerSubject(
* Necessary because check is protected and final in the Truth library
*/
fun verify(message: String): StandardSubjectBuilder = check(message)
-} \ No newline at end of file
+
+ companion object {
+ @VisibleForTesting
+ @JvmStatic
+ val ASSERTION_TAG = "Assertion"
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt
index fe7058d51..9113b2d19 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt
@@ -24,6 +24,7 @@ import android.os.RemoteException
import android.os.SystemClock
import android.util.Log
import android.util.Rational
+import android.view.Display
import android.view.Surface
import android.view.View
import android.view.ViewConfiguration
@@ -35,7 +36,8 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.helpers.WindowUtils.displayBounds
-import com.android.server.wm.flicker.helpers.WindowUtils.getNavigationBarPosition
+import com.android.server.wm.flicker.helpers.WindowUtils.estimateNavigationBarPosition
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.Assert
import org.junit.Assert.assertNotNull
@@ -112,7 +114,7 @@ fun UiDevice.openQuickstep(
navBar.visibleBounds
} else {
Log.e(TAG, "Could not find nav bar, infer location")
- getNavigationBarPosition(Surface.ROTATION_0).bounds
+ estimateNavigationBarPosition(Surface.ROTATION_0).bounds
}
val startX = navBarVisibleBounds.centerX()
@@ -151,8 +153,11 @@ fun UiDevice.openQuickstep(
recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
}
assertNotNull("Recent items didn't appear", recents)
- wmHelper.waitForNavBarStatusBarVisible()
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitFor(
+ WindowManagerConditionsFactory.isNavBarVisible(),
+ WindowManagerConditionsFactory.isStatusBarVisible(),
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY)
+ )
}
private fun getLauncherOverviewSelector(device: UiDevice): BySelector {
@@ -242,7 +247,9 @@ fun UiDevice.launchSplitScreen(
// Wait for animation to complete.
this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
- wmHelper.waitForSurfaceAppeared(DOCKED_STACK_DIVIDER)
+ wmHelper.waitFor(
+ WindowManagerConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER),
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
if (!this.isInSplitScreen()) {
Assert.fail("Unable to find Split screen divider")
@@ -270,8 +277,10 @@ fun UiDevice.isInSplitScreen(): Boolean {
return this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT) != null
}
-fun UiDevice.waitSplitScreenGone(): Boolean {
- return this.wait(Until.gone(splitScreenDividerSelector), FIND_TIMEOUT) != null
+fun waitSplitScreenGone(wmHelper: WindowManagerStateHelper): Boolean {
+ return wmHelper.waitFor(
+ WindowManagerConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER),
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
}
private val splitScreenDividerSelector: BySelector
@@ -303,7 +312,7 @@ fun UiDevice.exitSplitScreen() {
*
* @throws AssertionError when unable to find the split screen divider
*/
-fun UiDevice.exitSplitScreenFromBottom() {
+fun UiDevice.exitSplitScreenFromBottom(wmHelper: WindowManagerStateHelper) {
// Quickstep enabled
val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
assertNotNull("Unable to find Split screen divider", divider)
@@ -315,7 +324,7 @@ fun UiDevice.exitSplitScreenFromBottom() {
Point(this.displayWidth / 2, this.displayHeight)
}
divider.drag(dstPoint, 400)
- if (!this.waitSplitScreenGone()) {
+ if (!waitSplitScreenGone(wmHelper)) {
Assert.fail("Split screen divider never disappeared")
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/SampleAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/SampleAppHelper.kt
new file mode 100644
index 000000000..940c06367
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/helpers/SampleAppHelper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import com.android.server.wm.traces.common.FlickerComponentName
+
+/**
+ * Helper to launch the default browser app (compatible with AOSP)
+ *
+ * This helper has no other functionality but the app launch.
+ *
+ * This helper is used to launch an app after some operations (e.g., navigation mode change),
+ * so that the device is stable before executing flicker tests
+ */
+class SampleAppHelper(
+ instrumentation: Instrumentation,
+ private val pkgManager: PackageManager = instrumentation.context.packageManager
+) : StandardAppHelper(
+ instrumentation,
+ "SampleApp",
+ getBrowserComponent(pkgManager)
+) {
+ override fun getOpenAppIntent(): Intent =
+ pkgManager.getLaunchIntentForPackage(component.packageName)
+ ?: error("Unable to find intent for browser")
+
+ companion object {
+ private fun getBrowserIntent(): Intent {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ private fun getBrowserComponent(pkgManager: PackageManager): FlickerComponentName {
+ val intent = getBrowserIntent()
+ val resolveInfo = pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ ?: error("Unable to resolve browser activity")
+ return FlickerComponentName(resolveInfo.activityInfo.packageName, className = "")
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt
index 71338f1fb..20ebfdee4 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt
@@ -28,9 +28,8 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.parser.toActivityName
-import com.android.server.wm.traces.parser.toWindowName
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
/**
@@ -40,7 +39,7 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
open class StandardAppHelper @JvmOverloads constructor(
instr: Instrumentation,
@JvmField val appName: String,
- @JvmField val component: ComponentName,
+ @JvmField val component: FlickerComponentName,
protected val launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : AbstractStandardAppHelper(instr) {
@@ -52,10 +51,7 @@ open class StandardAppHelper @JvmOverloads constructor(
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
): this(instr, appName,
- ComponentName.createRelative(packageName, ".$activity"), launcherStrategy)
-
- val windowName: String = component.toWindowName()
- val activityName: String = component.toActivityName()
+ FlickerComponentName(packageName, ".$activity"), launcherStrategy)
private val activityManager: ActivityManager?
get() = mInstrumentation.context.getSystemService(ActivityManager::class.java)
@@ -88,7 +84,7 @@ open class StandardAppHelper @JvmOverloads constructor(
val intent = Intent()
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- intent.component = component
+ intent.component = ComponentName(component.packageName, component.className)
return intent
}
@@ -109,7 +105,6 @@ open class StandardAppHelper @JvmOverloads constructor(
/**
* Exits the activity and wait for activity destroyed
*/
- @JvmOverloads
fun exit(
wmHelper: WindowManagerStateHelper
) {
@@ -177,10 +172,11 @@ open class StandardAppHelper @JvmOverloads constructor(
val window = if (expectedWindowName.isNotEmpty()) {
expectedWindowName
} else {
- windowName
+ component.toWindowName()
}
wmHelper.waitFor("App is shown") {
- it.wmState.isComplete() && it.wmState.isWindowVisible(window)
+ it.wmState.isComplete() && it.wmState.isWindowVisible(window) &&
+ !it.layerState.isAnimating()
}
wmHelper.waitForAppTransitionIdle()
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt
index 2d1e4439a..f40ca50be 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt
@@ -23,8 +23,7 @@ import android.graphics.Region
import android.view.Surface
import android.view.WindowManager
import androidx.test.platform.app.InstrumentationRegistry
-import kotlin.math.max
-import kotlin.math.min
+import com.android.server.wm.traces.common.layers.Display
fun Int.isRotated() = this == Surface.ROTATION_90 || this == Surface.ROTATION_270
@@ -77,32 +76,54 @@ object WindowUtils {
}
/**
- * Gets the expected status bar position at a specific rotation
+ * Gets the expected status bar position for a specific display
*
- * @param requestedRotation Device rotation
+ * @param display the main display
*/
- fun getStatusBarPosition(requestedRotation: Int): Region {
- val displayBounds = displayBounds
- val resourceName: String
- val width: Int
- if (!requestedRotation.isRotated()) {
- resourceName = "status_bar_height_portrait"
- width = min(displayBounds.width(), displayBounds.height())
+ fun getStatusBarPosition(display: Display): Region {
+ val resourceName = if (!display.transform.getRotation().isRotated()) {
+ "status_bar_height_portrait"
} else {
- resourceName = "status_bar_height_landscape"
- width = max(displayBounds.width(), displayBounds.height())
+ "status_bar_height_landscape"
}
val resourceId = resources.getIdentifier(resourceName, "dimen", "android")
val height = resources.getDimensionPixelSize(resourceId)
- return Region(0, 0, width, height)
+ return Region(0, 0, display.layerStackSpace.width, height)
+ }
+
+ /**
+ * Gets the expected navigation bar position for a specific display
+ *
+ * @param display the main display
+ */
+ fun getNavigationBarPosition(display: Display): Region {
+ val navBarWidth = getDimensionPixelSize("navigation_bar_width")
+ val navBarHeight = navigationBarHeight
+ val displayHeight = display.layerStackSpace.height
+ val displayWidth = display.layerStackSpace.width
+ val requestedRotation = display.transform.getRotation()
+
+ return when {
+ // nav bar is at the bottom of the screen
+ requestedRotation in listOf(Surface.ROTATION_0, Surface.ROTATION_180) ||
+ isGesturalNavigationEnabled ->
+ Region(0, displayHeight - navBarHeight, displayWidth, displayHeight)
+ // nav bar is at the right side
+ requestedRotation == Surface.ROTATION_90 ->
+ Region(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
+ // nav bar is at the left side
+ requestedRotation == Surface.ROTATION_270 ->
+ Region(0, 0, navBarWidth, displayHeight)
+ else -> error("Unknown rotation $requestedRotation")
+ }
}
/**
- * Gets the expected navigation bar position at a specific rotation
+ * Estimate the navigation bar position at a specific rotation
*
* @param requestedRotation Device rotation
*/
- fun getNavigationBarPosition(requestedRotation: Int): Region {
+ fun estimateNavigationBarPosition(requestedRotation: Int): Region {
val displayBounds = displayBounds
val displayWidth: Int
val displayHeight: Int
@@ -168,4 +189,4 @@ object WindowUtils {
.getIdentifier("docked_stack_divider_insets", "dimen", "android")
return resources.getDimensionPixelSize(resourceId)
}
-} \ No newline at end of file
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt
index 6a87b51c1..88075e155 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt
@@ -17,10 +17,11 @@
@file:JvmName("Extensions")
package com.android.server.wm.flicker.monitor
-import com.android.server.wm.traces.parser.DeviceStateDump
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
import com.android.server.wm.flicker.getDefaultFlickerOutputDir
+import com.android.server.wm.traces.common.DeviceTraceDump
import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.parser.DeviceDumpParser
import com.android.server.wm.traces.parser.layers.LayersTraceParser
import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
import java.nio.file.Path
@@ -73,11 +74,11 @@ fun withSFTracing(
fun withTracing(
outputDir: Path = getDefaultFlickerOutputDir(),
predicate: () -> Unit
-): DeviceStateDump {
+): DeviceTraceDump {
val traces = recordTraces(outputDir, predicate)
val wmTraceData = traces.first
val layersTraceData = traces.second
- return DeviceStateDump.fromTrace(wmTraceData, layersTraceData)
+ return DeviceDumpParser.fromTrace(wmTraceData, layersTraceData)
}
/**
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt
index 8e5a71689..45ffcda26 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt
@@ -39,12 +39,6 @@ interface ITransitionMonitor {
save("${testTag}_$iteration", flickerRunResultBuilder)
/**
- * Saves any monitor artifacts to file adding `testTag` to the file name.
- *
- * @param testTag suffix added to artifact name
- * @return Path to saved artifact
- */
- /**
* Saves trace file to the external storage directory suffixing the name with the testtag and
* iteration.
*
@@ -58,7 +52,4 @@ interface ITransitionMonitor {
fun save(testTag: String, flickerRunResultBuilder: FlickerRunResult.Builder) {
throw UnsupportedOperationException("Save not implemented for this monitor")
}
-
- val checksum: String
- get() = throw UnsupportedOperationException("Checksum not implemented for this monitor")
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
index 74fa38852..b9f16cdbc 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
@@ -31,7 +31,7 @@ import java.nio.file.Path
open class LayersTraceMonitor(
outputDir: Path,
private val traceFlags: Int
-) : TransitionMonitor(outputDir, "layers_trace.pb") {
+) : TransitionMonitor(outputDir, "layers_trace$WINSCOPE_EXT") {
constructor(outputDir: Path) : this(outputDir, TRACE_FLAGS)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt
index 92409958a..d9418332f 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt
@@ -19,14 +19,8 @@ package com.android.server.wm.flicker.monitor
import androidx.annotation.VisibleForTesting
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.FlickerRunResult
-import com.google.common.io.BaseEncoding
-import java.io.FileInputStream
-import java.io.IOException
-import java.nio.ByteBuffer
import java.nio.file.Files
import java.nio.file.Path
-import java.security.MessageDigest
-import java.security.NoSuchAlgorithmException
/**
* Base class for monitors containing common logic to read the trace as a byte array and save the
@@ -36,9 +30,6 @@ abstract class TraceMonitor internal constructor(
@VisibleForTesting var outputPath: Path,
protected var sourceTraceFilePath: Path
) : ITransitionMonitor {
- override var checksum: String = ""
- protected set
-
abstract val isEnabled: Boolean
abstract fun setResult(flickerRunResultBuilder: FlickerRunResult.Builder, traceFile: Path)
@@ -50,8 +41,6 @@ abstract class TraceMonitor internal constructor(
require(Files.exists(savedTrace)) { "Unable to save trace file $savedTrace" }
setResult(flickerRunResultBuilder, savedTrace)
-
- checksum = calculateChecksum(savedTrace)
}
fun save(testTag: String) {
@@ -59,8 +48,6 @@ abstract class TraceMonitor internal constructor(
val savedTrace = outputPath.resolve("${testTag}_${sourceTraceFilePath.fileName}")
moveFile(sourceTraceFilePath, savedTrace)
require(Files.exists(savedTrace)) { "Unable to save trace file $savedTrace" }
-
- checksum = calculateChecksum(savedTrace)
}
private fun moveFile(src: Path, dst: Path) {
@@ -74,28 +61,4 @@ abstract class TraceMonitor internal constructor(
SystemUtil.runShellCommand("chmod a+r $dst")
SystemUtil.runShellCommand("rm $src")
}
-
- companion object {
- @VisibleForTesting
- @JvmStatic
- fun calculateChecksum(traceFile: Path): String {
- return try {
- val messageDigest = MessageDigest.getInstance("SHA-256")
- val inputStream = FileInputStream(traceFile.toFile())
- val channel = inputStream.channel
- val buffer = ByteBuffer.allocate(2048)
- while (channel.read(buffer) != -1) {
- buffer.flip()
- messageDigest.update(buffer)
- buffer.clear()
- }
- val hash = messageDigest.digest()
- BaseEncoding.base16().encode(hash).toLowerCase()
- } catch (e: NoSuchAlgorithmException) {
- throw IllegalArgumentException("Checksum algorithm SHA-256 not found", e)
- } catch (e: IOException) {
- throw IllegalArgumentException("File not found", e)
- }
- }
- }
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt
index 3424aa241..3df800747 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt
@@ -61,5 +61,6 @@ abstract class TransitionMonitor(
companion object {
private val TRACE_DIR = Paths.get("/data/misc/wmtrace/")
+ internal const val WINSCOPE_EXT = ".winscope"
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
index 0e46c81ce..11858cbd2 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
@@ -30,7 +30,7 @@ import java.nio.file.Path
*/
open class WindowManagerTraceMonitor(
outputDir: Path
-) : TransitionMonitor(outputDir, "wm_trace.pb") {
+) : TransitionMonitor(outputDir, "wm_trace$WINSCOPE_EXT") {
private val windowManager = WindowManagerGlobal.getWindowManagerService()
override fun start() {
try {
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt
new file mode 100644
index 000000000..d425775a2
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.rules
+
+import android.app.Instrumentation
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Launched an app before the test
+ *
+ * @param instrumentation Instrumentation mechanism to use
+ * @param wmHelper WM/SF synchronization helper
+ * @param appHelper App to launch
+ */
+class LaunchAppRule @JvmOverloads constructor(
+ private val appHelper: StandardAppHelper,
+ private val instrumentation: Instrumentation = appHelper.mInstrumentation,
+ private val wmHelper: WindowManagerStateHelper = WindowManagerStateHelper()
+) : TestWatcher() {
+ @JvmOverloads
+ constructor(
+ component: FlickerComponentName,
+ appName: String = "",
+ instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ wmHelper: WindowManagerStateHelper = WindowManagerStateHelper()
+ ): this(StandardAppHelper(instrumentation, appName, component), instrumentation, wmHelper)
+
+ override fun starting(description: Description?) {
+ appHelper.launchViaIntent()
+ appHelper.exit(wmHelper)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRule.kt
new file mode 100644
index 000000000..165fa8674
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRule.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.rules
+
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.getDefaultFlickerOutputDir
+import com.android.server.wm.flicker.monitor.LayersTraceMonitor
+import com.android.server.wm.flicker.monitor.ScreenRecorder
+import com.android.server.wm.flicker.monitor.TraceMonitor
+import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor
+import com.android.server.wm.flicker.service.FlickerService
+import com.android.server.wm.flicker.service.FlickerService.Companion.getFassFilePath
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+import com.android.server.wm.traces.parser.layers.LayersTraceParser
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import java.nio.file.Files
+import java.nio.file.Path
+
+/**
+ * Collect the WM and SF traces, parse them and call the WM Flicker Service after the test
+ */
+open class WMFlickerServiceRule @JvmOverloads constructor(
+ private val outputDir: Path = getDefaultFlickerOutputDir()
+) : TestWatcher() {
+ private val traceMonitors = mutableListOf<TraceMonitor>()
+
+ protected var wmTrace: WindowManagerTrace = WindowManagerTrace(emptyArray(), source = "")
+ protected var layersTrace: LayersTrace = LayersTrace(emptyArray(), source = "")
+
+ override fun starting(description: Description?) {
+ setupMonitors()
+ cleanupTraceFiles()
+ traceMonitors.forEach {
+ it.start()
+ }
+ }
+
+ override fun finished(description: Description?) {
+ val testTag = description?.methodName ?: "fass"
+ traceMonitors.forEach {
+ it.stop()
+ it.save(testTag)
+ }
+
+ Files.createDirectories(outputDir)
+ wmTrace = getWindowManagerTrace(getFassFilePath(outputDir, testTag, "wm_trace"))
+ layersTrace = getLayersTrace(getFassFilePath(outputDir, testTag, "layers_trace"))
+
+ val flickerService = FlickerService()
+ flickerService.process(wmTrace, layersTrace, outputDir, testTag)
+ }
+
+ private fun setupMonitors() {
+ traceMonitors.add(WindowManagerTraceMonitor(outputDir))
+ traceMonitors.add(LayersTraceMonitor(outputDir))
+ traceMonitors.add(ScreenRecorder(
+ outputDir,
+ InstrumentationRegistry.getInstrumentation().targetContext)
+ )
+ }
+
+ /**
+ * Remove the WM trace and layers trace files collected from previous test runs.
+ */
+ private fun cleanupTraceFiles() {
+ Files.list(outputDir).forEach { file ->
+ if (!Files.isDirectory(file)) {
+ Files.delete(file)
+ }
+ }
+ }
+
+ /**
+ * Parse the window manager trace file.
+ *
+ * @param traceFilePath
+ * @return parsed window manager trace.
+ */
+ private fun getWindowManagerTrace(traceFilePath: Path): WindowManagerTrace {
+ val wmTraceByteArray: ByteArray = Files.readAllBytes(traceFilePath)
+ return WindowManagerTraceParser.parseFromTrace(wmTraceByteArray)
+ }
+
+ /**
+ * Parse the layers trace file.
+ *
+ * @param traceFilePath
+ * @return parsed layers trace.
+ */
+ private fun getLayersTrace(traceFilePath: Path): LayersTrace {
+ val layersTraceByteArray: ByteArray = Files.readAllBytes(traceFilePath)
+ return LayersTraceParser.parseFromTrace(layersTraceByteArray)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRuleForTestSpec.kt b/libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRuleForTestSpec.kt
new file mode 100644
index 000000000..5c3606d94
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/rules/WMFlickerServiceRuleForTestSpec.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.rules
+
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.dsl.AssertionTag
+import com.android.server.wm.flicker.service.FlickerService
+import com.android.server.wm.flicker.service.assertors.AssertionConfigParser
+import com.android.server.wm.flicker.service.assertors.AssertionData
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import org.junit.rules.TestWatcher
+
+/**
+ * A test rule reusing flicker data from [FlickerTestParameter], and fetching the traces
+ * to call the WM Flicker Service after the test
+ */
+@Deprecated("This test rule should be only used with legacy flicker tests. " +
+ "For new tests use WMFlickerServiceRule instead")
+class WMFlickerServiceRuleForTestSpec(
+ private val testSpec: FlickerTestParameter
+) : TestWatcher() {
+ private fun checkFlicker(category: String): List<ErrorTrace> {
+ // run flicker if it was not executed before
+ testSpec.flicker.result ?: testSpec.assertWm { isNotEmpty() }
+
+ val errors = mutableListOf<ErrorTrace>()
+ val result = testSpec.flicker.result ?: error("No flicker results for ${testSpec.flicker}")
+ val assertions = AssertionData.readConfiguration().filter { it.category == category }
+ val flickerService = FlickerService(assertions)
+
+ result.runs
+ .filter { it.assertionTag == AssertionTag.ALL }
+ .filter {
+ val hasWmTrace = it.wmSubject?.let { true } ?: false
+ val hasLayersTrace = it.layersSubject?.let { true } ?: false
+ hasWmTrace || hasLayersTrace
+ }
+ .forEach { run ->
+ val wmSubject = run.wmSubject as WindowManagerTraceSubject
+ val layersSubject = run.layersSubject as LayersTraceSubject
+
+ val outputDir = run.traceFiles
+ .firstOrNull()
+ ?.parent
+ ?: error("Output dir not detected")
+
+ val wmTrace = wmSubject.trace
+ val layersTrace = layersSubject.trace
+ errors.add(flickerService.process(wmTrace, layersTrace, outputDir, category))
+ }
+
+ return errors
+ }
+
+ fun checkPresubmitAssertions() {
+ val errors = checkFlicker(AssertionConfigParser.PRESUBMIT_KEY)
+ failIfAnyError(errors)
+ }
+
+ fun checkPostsubmitAssertions() {
+ val errors = checkFlicker(AssertionConfigParser.POSTSUBMIT_KEY)
+ failIfAnyError(errors)
+ }
+
+ fun checkFlakyAssertions() {
+ val errors = checkFlicker(AssertionConfigParser.FLAKY_KEY)
+ failIfAnyError(errors)
+ }
+
+ private fun failIfAnyError(errors: List<ErrorTrace>) {
+ val errorMsg = errors.joinToString("\n") { runs ->
+ runs.entries.joinToString { state ->
+ state.errors.joinToString { "${it.assertionName}\n${it.message}" }
+ }
+ }
+ if (errorMsg.isNotEmpty()) {
+ error(errorMsg)
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/AssertionEngine.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/AssertionEngine.kt
new file mode 100644
index 000000000..c18144ed1
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/AssertionEngine.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import com.android.server.wm.flicker.service.assertors.AssertionData
+import com.android.server.wm.flicker.service.assertors.TransitionAssertor
+import com.android.server.wm.traces.common.errors.ErrorState
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.TagTrace
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.tags.TransitionTag
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * Invokes the configured assertors and summarizes the results.
+ */
+class AssertionEngine(
+ private val assertions: List<AssertionData>,
+ private val logger: (String) -> Unit
+) {
+ private val knownTypes = assertions.map { it.transitionType }
+
+ fun analyze(
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace,
+ tagTrace: TagTrace
+ ): ErrorTrace {
+ val errors = mutableListOf<ErrorState>()
+ val allTransitions = getTransitionTags(tagTrace)
+
+ allTransitions
+ .filter { knownTypes.contains(it.tag.transition) }
+ .forEach { transition ->
+ val (filteredWmTrace, filteredLayersTrace) =
+ splitTraces(transition, wmTrace, layersTrace)
+
+ val assertionsOfType = assertions
+ .filter { it.transitionType == transition.tag.transition }
+ val assertor = TransitionAssertor(assertionsOfType, logger)
+ val errorTrace = assertor.analyze(
+ transition.tag, filteredWmTrace, filteredLayersTrace)
+ errors.addAll(errorTrace)
+ }
+
+ /* Ensure all error states with same timestamp are merged */
+ val errorStates = errors.distinct()
+ .groupBy({ it.timestamp }, { it.errors.asList() })
+ .mapValues { (key, value) ->
+ ErrorState(value.flatten().toTypedArray(), key.toString()) }
+ .values.toTypedArray()
+
+ return ErrorTrace(errorStates, source = "")
+ }
+
+ /**
+ * Extracts all [TransitionTag]s from a [TagTrace].
+ *
+ * @param tagTrace Tag Trace
+ * @return a list with [TransitionTag]
+ */
+ fun getTransitionTags(tagTrace: TagTrace): List<TransitionTag> {
+ return tagTrace.entries.flatMap { state ->
+ state.tags.filter { tag -> tag.isStartTag }
+ .map {
+ TransitionTag(
+ tag = it,
+ startTimestamp = state.timestamp,
+ endTimestamp = getEndTagTimestamp(tagTrace, it)
+ )
+ }
+ }
+ }
+
+ private fun getEndTagTimestamp(tagTrace: TagTrace, tag: Tag): Long {
+ val finalTag = tag.copy(isStartTag = false)
+ return tagTrace.entries.firstOrNull { state -> state.tags.contains(finalTag) }?.timestamp
+ ?: throw RuntimeException("All open tags should be closed!")
+ }
+
+ /**
+ * Splits a [WindowManagerTrace] and a [LayersTrace] by a [Transition].
+ *
+ * @param tag a list with all [TransitionTag]s
+ * @param wmTrace Window Manager trace
+ * @param layersTrace Surface Flinger trace
+ * @return a list with [WindowManagerTrace] blocks
+ */
+ fun splitTraces(
+ tag: TransitionTag,
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace
+ ): Pair<WindowManagerTrace, LayersTrace> {
+ val filteredWmTrace = wmTrace.filter(tag.startTimestamp, tag.endTimestamp)
+ val filteredLayersTrace = layersTrace.filter(tag.startTimestamp, tag.endTimestamp)
+ return Pair(filteredWmTrace, filteredLayersTrace)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt
new file mode 100644
index 000000000..827b586ed
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import android.util.Log
+import com.android.server.wm.flicker.FLICKER_TAG
+import com.android.server.wm.flicker.monitor.TransitionMonitor.Companion.WINSCOPE_EXT
+import com.android.server.wm.flicker.service.assertors.AssertionData
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.service.TaggingEngine
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+import com.android.server.wm.traces.parser.errors.writeToFile
+import com.android.server.wm.traces.parser.tags.writeToFile
+import java.nio.file.Path
+
+/**
+ * Contains the logic for Flicker as a Service.
+ */
+class FlickerService @JvmOverloads constructor(
+ private val assertions: List<AssertionData> = AssertionData.readConfiguration()
+) {
+ /**
+ * The entry point for WM Flicker Service.
+ *
+ * Calls the Tagging Engine and the Assertion Engine.
+ *
+ * @param wmTrace Window Manager trace
+ * @param layersTrace Surface Flinger trace
+ * @return A list containing all failures
+ */
+ fun process(
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace,
+ outputDir: Path,
+ testTag: String
+ ): ErrorTrace {
+ val taggingEngine = TaggingEngine(wmTrace, layersTrace) { Log.v("$FLICKER_TAG-PROC", it) }
+ val tagTrace = taggingEngine.run()
+ val tagTraceFile = getFassFilePath(outputDir, testTag, "tag_trace")
+ tagTrace.writeToFile(tagTraceFile)
+
+ val assertionEngine = AssertionEngine(assertions) { Log.v("$FLICKER_TAG-ASSERT", it) }
+ val errorTrace = assertionEngine.analyze(wmTrace, layersTrace, tagTrace)
+ val errorTraceFile = getFassFilePath(outputDir, testTag, "error_trace")
+ errorTrace.writeToFile(errorTraceFile)
+ return errorTrace
+ }
+
+ companion object {
+ /**
+ * Returns the computed path for the Fass files.
+ *
+ * @param outputDir the output directory for the trace file
+ * @param testTag the tag to identify the test
+ * @param file the name of the trace file
+ * @return the path to the trace file
+ */
+ internal fun getFassFilePath(outputDir: Path, testTag: String, file: String): Path =
+ outputDir.resolve("${testTag}_$file$WINSCOPE_EXT")
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParser.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParser.kt
new file mode 100644
index 000000000..cc2d83930
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParser.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import android.util.Log
+import com.android.server.wm.flicker.FLICKER_TAG
+import com.android.server.wm.traces.common.tags.Transition
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+
+object AssertionConfigParser {
+ private const val ASSERTORS_KEY = "assertors"
+ private const val CLASS_KEY = "class"
+ private const val ARGS_KEY = "args"
+ private const val TRANSITION_KEY = "transition"
+ private const val ASSERTIONS_KEY = "assertions"
+
+ internal const val PRESUBMIT_KEY = "presubmit"
+ internal const val POSTSUBMIT_KEY = "postsubmit"
+ internal const val FLAKY_KEY = "flaky"
+
+ /**
+ * Parses assertor config JSON file. The format expected is:
+ * <pre>
+ * {
+ * "assertors": [
+ * {
+ * "transition": "ROTATION",
+ * "assertions": {
+ * "presubmit": [
+ * "navBarWindowIsVisible"
+ * "navBarLayerIsVisible",
+ * "navBarLayerRotatesAndScales"
+ * ],
+ * "postsubmit": [ ],
+ * "flaky": [
+ * "entireScreenCovered"
+ * ]
+ * }
+ * }
+ * ]
+ * }
+ * </pre>
+ *
+ * @param config string containing a json file
+ * @return a list of [AssertionData] assertions
+ */
+ @JvmStatic
+ fun parseConfigFile(config: String): List<AssertionData> {
+ val assertorsConfig = mutableListOf<AssertionData>()
+ val jsonArray = JSONObject(config).getJSONArray(ASSERTORS_KEY)
+
+ for (i in 0 until jsonArray.length()) {
+ val jsonObject = jsonArray.getJSONObject(i)
+ val jsonAssertions = jsonObject.getJSONObject(ASSERTIONS_KEY)
+ val transitionType = Transition.valueOf(jsonObject.getString(TRANSITION_KEY))
+ val presubmit = parseAssertionArray(
+ jsonAssertions.getJSONArray(PRESUBMIT_KEY), transitionType, PRESUBMIT_KEY)
+ val postsubmit = parseAssertionArray(
+ jsonAssertions.getJSONArray(POSTSUBMIT_KEY), transitionType, POSTSUBMIT_KEY)
+ val flaky = parseAssertionArray(
+ jsonAssertions.getJSONArray(FLAKY_KEY), transitionType, FLAKY_KEY)
+ val assertionsList = presubmit + postsubmit + flaky
+
+ assertorsConfig.addAll(assertionsList)
+ }
+
+ return assertorsConfig
+ }
+
+ /**
+ * Splits an assertions JSONArray into an array of [AssertionData].
+ *
+ * @param assertionsArray a [JSONArray] with assertion names
+ * @param transitionType type of transition connected to this assertion
+ * @param category the category of the assertion (presubmit/postsubmit/flaky)
+ * @return an array of assertion details
+ */
+ @JvmStatic
+ private fun parseAssertionArray(
+ assertionsArray: JSONArray,
+ transitionType: Transition,
+ category: String
+ ): List<AssertionData> {
+ val assertions = mutableListOf<AssertionData>()
+ try {
+ for (i in 0 until assertionsArray.length()) {
+ val assertionObj = assertionsArray.getJSONObject(i)
+ val assertionClass = assertionObj.getString(CLASS_KEY)
+ val args = mutableListOf<String>()
+ if (assertionObj.has(ARGS_KEY)) {
+ val assertionArgsArray = assertionObj.getJSONArray(ARGS_KEY)
+ for (j in 0 until assertionArgsArray.length()) {
+ val arg = assertionArgsArray.getString(j)
+ args.add(arg)
+ }
+ }
+ Log.v(FLICKER_TAG, "Creating assertion for class $assertionClass")
+ assertions.add(
+ AssertionData.fromString(transitionType, category, assertionClass, args))
+ }
+ } catch (e: JSONException) {
+ throw RuntimeException(e)
+ }
+
+ return assertions
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionData.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionData.kt
new file mode 100644
index 000000000..768b867fe
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/AssertionData.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import com.android.server.wm.flicker.service.FlickerService
+import com.android.server.wm.traces.common.tags.Transition
+import java.io.FileNotFoundException
+
+/**
+ * Stores data for FASS assertions.
+ */
+data class AssertionData(
+ val transitionType: Transition,
+ val assertion: BaseAssertion,
+ val category: String
+) {
+ companion object {
+ /**
+ * Returns the name of the assertors configuration file.
+ */
+ private const val CONFIG_FILE_NAME = "config.json"
+
+ /**
+ * Creates an assertion data based on it's fully-qualified class path [cls] and set
+ * its category to [category]
+ */
+ fun fromString(
+ transitionType: Transition,
+ category: String,
+ cls: String,
+ args: List<String>
+ ): AssertionData {
+ val clsDescriptor = Class.forName(cls)
+
+ val assertionObj = if (args.isEmpty()) {
+ clsDescriptor.newInstance() as BaseAssertion
+ } else {
+ val ctor = clsDescriptor.constructors
+ .firstOrNull { it.parameterCount == args.size }
+ ?: error("Constructor not found")
+ ctor.newInstance(*args.toTypedArray()) as BaseAssertion
+ }
+
+ return AssertionData(transitionType, assertionObj, category)
+ }
+
+ /**
+ * Reads the assertions configuration for the configuration file.
+ *
+ * @param fileName the location of the configuration file
+ * @return a list with assertors configuration
+ *
+ * @throws FileNotFoundException when there is no config file
+ */
+ @JvmOverloads
+ fun readConfiguration(fileName: String = CONFIG_FILE_NAME): List<AssertionData> {
+ val fileContent = FlickerService::class.java.classLoader.getResource(fileName)
+ ?.readText(Charsets.UTF_8)
+ ?: throw FileNotFoundException("A configuration file must exist!")
+ return AssertionConfigParser.parseConfigFile(fileContent)
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/BaseAssertion.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/BaseAssertion.kt
new file mode 100644
index 000000000..0815e0357
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/BaseAssertion.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import com.android.server.wm.flicker.assertions.FlickerSubject
+import com.android.server.wm.flicker.traces.FlickerSubjectException
+import com.android.server.wm.flicker.traces.layers.LayerSubject
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowStateSubject
+import com.android.server.wm.traces.common.layers.Layer
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.TransitionTag
+import com.android.server.wm.traces.common.windowmanager.windows.WindowState
+
+/**
+ * Base calss for a FASS assertion
+ */
+abstract class BaseAssertion {
+ private var failureSubject: FlickerSubject? = null
+
+ /**
+ * Assertion name
+ */
+ val name: String = this::class.java.simpleName
+
+ /**
+ * Run specific assertion evaluation block
+ *
+ * @param tag a list with all [TransitionTag]s
+ * @param wmSubject Window Manager trace subject
+ * @param layerSubject Surface Flinger trace subject
+ */
+ protected abstract fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ )
+
+ /**
+ * Evaluate the assertion on a transition [Tag] in a [WindowManagerTraceSubject] and
+ * [LayersTraceSubject]
+ *
+ * @param tag a list with all [TransitionTag]s
+ * @param wmSubject Window Manager trace subject
+ * @param layerSubject Surface Flinger trace subject
+ */
+ fun evaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ try {
+ doEvaluate(tag, wmSubject, layerSubject)
+ } catch (e: FlickerSubjectException) {
+ failureSubject = e.subject
+ throw e
+ }
+ }
+
+ /**
+ * Returns the layer responsible for the failure, if any
+ *
+ * @param tag a list with all [TransitionTag]s
+ * @param wmSubject Window Manager trace subject
+ * @param layerSubject Surface Flinger trace subject
+ */
+ open fun getFailureLayer(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ): Layer? {
+ val failureSubject = failureSubject
+ return if (failureSubject is LayerSubject) {
+ failureSubject.layer
+ } else {
+ null
+ }
+ }
+
+ /**
+ * Returns the window responsible for the last failure, if any
+ *
+ * @param tag a list with all [TransitionTag]s
+ * @param wmSubject Window Manager trace subject
+ * @param layerSubject Surface Flinger trace subject
+ */
+ open fun getFailureWindow(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ): WindowState? {
+ val failureSubject = failureSubject
+ return if (failureSubject is WindowStateSubject) {
+ failureSubject.windowState
+ } else {
+ null
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/Components.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/Components.kt
new file mode 100644
index 000000000..c6d9a76b7
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/Components.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import com.android.server.wm.traces.common.FlickerComponentName
+
+object Components {
+ val LAUNCHER = FlickerComponentName("com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/TransitionAssertor.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/TransitionAssertor.kt
new file mode 100644
index 000000000..9ec5a2908
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/TransitionAssertor.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import android.util.Log
+import com.android.server.wm.flicker.FLICKER_TAG
+import com.android.server.wm.flicker.traces.FlickerSubjectException
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.errors.Error
+import com.android.server.wm.traces.common.errors.ErrorState
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.service.ITransitionAssertor
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * Class that runs FASS assertions.
+ */
+class TransitionAssertor(
+ private val assertions: List<AssertionData>,
+ private val logger: (String) -> Unit
+) : ITransitionAssertor {
+ /** {@inheritDoc} */
+ override fun analyze(
+ tag: Tag,
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace
+ ): ErrorTrace {
+ val errorStates = mutableMapOf<Long, MutableList<Error>>()
+
+ errorStates.putAll(
+ runCategoryAssertions(tag, wmTrace, layersTrace, AssertionConfigParser.PRESUBMIT_KEY))
+ errorStates.putAll(
+ runCategoryAssertions(tag, wmTrace, layersTrace, AssertionConfigParser.POSTSUBMIT_KEY))
+ errorStates.putAll(
+ runCategoryAssertions(tag, wmTrace, layersTrace, AssertionConfigParser.FLAKY_KEY))
+
+ return buildErrorTrace(errorStates)
+ }
+
+ private fun runCategoryAssertions(
+ tag: Tag,
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace,
+ categoryKey: String
+ ): Map<Long, MutableList<Error>> {
+ logger.invoke("Running assertions for $tag $categoryKey")
+ val wmSubject = WindowManagerTraceSubject.assertThat(wmTrace)
+ val layersSubject = LayersTraceSubject.assertThat(layersTrace)
+ val assertions = assertions.filter { it.category == categoryKey }
+ return runAssertionsOnSubjects(tag, wmSubject, layersSubject, assertions)
+ }
+
+ private fun runAssertionsOnSubjects(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject,
+ assertions: List<AssertionData>
+ ): Map<Long, MutableList<Error>> {
+ val errors = mutableMapOf<Long, MutableList<Error>>()
+
+ try {
+ assertions.forEach {
+ val assertion = it.assertion
+ logger.invoke("Running assertion $assertion")
+ val result = assertion.runCatching { evaluate(tag, wmSubject, layerSubject) }
+ if (result.isFailure) {
+ val layer = assertion.getFailureLayer(tag, wmSubject, layerSubject)
+ val window = assertion.getFailureWindow(tag, wmSubject, layerSubject)
+ val exception = result.exceptionOrNull() as FlickerSubjectException
+
+ errors.putIfAbsent(exception.timestamp, mutableListOf())
+ val errorEntry = Error(
+ stacktrace = exception.stackTraceToString(),
+ message = exception.message,
+ layerId = layer?.id ?: 0,
+ windowToken = window?.token ?: "",
+ assertionName = assertion.name
+ )
+ errors.getValue(exception.timestamp).add(errorEntry)
+ }
+ }
+ } catch (e: NoSuchMethodException) {
+ Log.e("$FLICKER_TAG-ASSERT", "Assertion method not found", e)
+ } catch (e: SecurityException) {
+ Log.e("$FLICKER_TAG-ASSERT", "Unable to get assertion method", e)
+ }
+
+ return errors
+ }
+
+ private fun buildErrorTrace(errors: MutableMap<Long, MutableList<Error>>): ErrorTrace {
+ val errorStates = errors.map { entry ->
+ val timestamp = entry.key
+ val stateTags = entry.value
+ ErrorState(stateTags.toTypedArray(), timestamp.toString())
+ }
+ return ErrorTrace(errorStates.toTypedArray(), source = "")
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppComponentBaseTest.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppComponentBaseTest.kt
new file mode 100644
index 000000000..1fdce0c13
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppComponentBaseTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.FlickerSubjectException
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.tags.Tag
+
+abstract class AppComponentBaseTest : BaseAssertion() {
+ protected fun getComponentName(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject
+ ): FlickerComponentName {
+ try {
+ val windowName = getWindowState(tag, wmSubject).name
+ return FlickerComponentName.unflattenFromString(windowName)
+ } catch (e: Throwable) {
+ throw FlickerSubjectException(wmSubject, e)
+ }
+ }
+
+ private fun getWindowState(tag: Tag, wmSubject: WindowManagerTraceSubject) =
+ wmSubject.subjects.last().windowState {
+ it.layerId == tag.layerId || it.token == tag.windowToken
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtEnd.kt
new file mode 100644
index 000000000..58a4e20af
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtEnd.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [getWindowState] layer is invisible at the end of the transition
+ */
+class AppLayerIsInvisibleAtEnd : AppComponentBaseTest() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.last().isInvisible(getComponentName(tag, wmSubject))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtStart.kt
new file mode 100644
index 000000000..2c40e5713
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsInvisibleAtStart.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [getWindowState] layer is invisible at the start of the transition
+ */
+class AppLayerIsInvisibleAtStart : AppComponentBaseTest() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.first().isInvisible(getComponentName(tag, wmSubject))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtEnd.kt
new file mode 100644
index 000000000..3670d26db
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtEnd.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [getWindowState] layer is visible at the end of the transition
+ */
+class AppLayerIsVisibleAtEnd : AppComponentBaseTest() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.last().isVisible(getComponentName(tag, wmSubject))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtStart.kt
new file mode 100644
index 000000000..479c71085
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerIsVisibleAtStart.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [getWindowState] layer is visible at the start of the transition
+ */
+class AppLayerIsVisibleAtStart : AppComponentBaseTest() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.first().isVisible(getComponentName(tag, wmSubject))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerReplacesLauncher.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerReplacesLauncher.kt
new file mode 100644
index 000000000..1591f7f2e
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppLayerReplacesLauncher.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.Components
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Asserts that:
+ * [Components.LAUNCHER] is visible at the start of the trace
+ * [Components.LAUNCHER] becomes invisible during the trace and (in the same entry)
+ * [getWindowState] becomes visible
+ * [getWindowState] remains visible until the end of the trace
+ */
+class AppLayerReplacesLauncher : AppComponentBaseTest() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.isVisible(Components.LAUNCHER)
+ .then()
+ .isVisible(getComponentName(tag, wmSubject))
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppWindowReplacesLauncherAsTopWindow.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppWindowReplacesLauncherAsTopWindow.kt
new file mode 100644
index 000000000..958327dea
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/AppWindowReplacesLauncherAsTopWindow.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.Components
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that [Components.LAUNCHER] is the top visible app window at the start of the transition
+ * and that it is replaced by [getWindowState] during the transition
+ */
+class AppWindowReplacesLauncherAsTopWindow : AppComponentBaseTest() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isAppWindowOnTop(Components.LAUNCHER)
+ .then()
+ .isAppWindowOnTop(getComponentName(tag, wmSubject))
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/ComponentBaseTest.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/ComponentBaseTest.kt
new file mode 100644
index 000000000..30288ea56
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/ComponentBaseTest.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.traces.common.FlickerComponentName
+
+abstract class ComponentBaseTest(windowName: String) : BaseAssertion() {
+ protected val component = FlickerComponentName.unflattenFromString(windowName)
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAlways.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAlways.kt
new file mode 100644
index 000000000..9820eaac3
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAlways.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the stack space of all displays is fully covered by any visible layer,
+ * during the whole transitions
+ */
+class EntireScreenCoveredAlways : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.invoke("entireScreenCovered") { entry ->
+ entry.entry.displays.forEach { display ->
+ entry.visibleRegion().coversAtLeast(display.layerStackSpace)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtEnd.kt
new file mode 100644
index 000000000..62b25397c
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtEnd.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the stack space of all displays is fully covered by any visible layer,
+ * at the end of the transition
+ */
+class EntireScreenCoveredAtEnd : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val subject = layerSubject.last()
+ subject.entry.displays.forEach { display ->
+ subject.visibleRegion().coversAtLeast(display.layerStackSpace)
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtStart.kt
new file mode 100644
index 000000000..c51eb51e3
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/EntireScreenCoveredAtStart.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the stack space of all displays is fully covered by any visible layer,
+ * at the start of the transition
+ */
+class EntireScreenCoveredAtStart : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val subject = layerSubject.first()
+ subject.entry.displays.forEach { display ->
+ subject.visibleRegion().coversAtLeast(display.layerStackSpace)
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesOutOfTop.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesOutOfTop.kt
new file mode 100644
index 000000000..4025a9567
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesOutOfTop.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.Components
+
+/**
+ * Checks that [Components.LAUNCHER] starts on top and moves out of top during the transition
+ */
+class LauncherWindowMovesOutOfTop : WindowMovesOutOfTop(Components.LAUNCHER.toWindowName()) \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesToTop.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesToTop.kt
new file mode 100644
index 000000000..821510171
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowMovesToTop.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.Components
+
+/**
+ * Checks that [Components.LAUNCHER] starts not on top and moves to top during the transition
+ */
+class LauncherWindowMovesToTop : WindowMovesToTop(Components.LAUNCHER.toWindowName()) \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowReplacesAppAsTopWindow.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowReplacesAppAsTopWindow.kt
new file mode 100644
index 000000000..a62de1d7b
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LauncherWindowReplacesAppAsTopWindow.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.Components
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that [getWindowState] is the top visible app window at the start of the transition and
+ * that it is replaced by [Components.LAUNCHER] during the transition
+ */
+class LauncherWindowReplacesAppAsTopWindow : AppComponentBaseTest() {
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isAppWindowOnTop(getComponentName(tag, wmSubject))
+ .then()
+ .isAppWindowOnTop(Components.LAUNCHER)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAlways.kt
new file mode 100644
index 000000000..150c2a9f9
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAlways.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] layer is invisible during the entire transition
+ */
+class LayerIsInvisibleAlways(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.isVisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtEnd.kt
new file mode 100644
index 000000000..198302bee
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtEnd.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] layer is invisible at the end of the transition
+ */
+class LayerIsInvisibleAtEnd(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.last().isInvisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtStart.kt
new file mode 100644
index 000000000..e241b7e04
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsInvisibleAtStart.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] layer is invisible at the start of the transition
+ */
+class LayerIsInvisibleAtStart(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.first().isInvisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAlways.kt
new file mode 100644
index 000000000..a4c126577
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAlways.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] layer is visible during the entire transition
+ */
+class LayerIsVisibleAlways(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.isVisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtEnd.kt
new file mode 100644
index 000000000..5d012ce98
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtEnd.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] layer is visible at the end of the transition
+ */
+class LayerIsVisibleAtEnd(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.last().isVisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtStart.kt
new file mode 100644
index 000000000..0d61d8779
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/LayerIsVisibleAtStart.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] layer is visible at the start of the transition
+ */
+class LayerIsVisibleAtStart(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.first().isVisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtEnd.kt
new file mode 100644
index 000000000..4ab581995
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtEnd.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [FlickerComponentName.NAV_BAR] layer is placed at the correct position at the
+ * end of the transition
+ */
+class NavBarLayerPositionAtEnd : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val lastLayersSubject = layerSubject.last()
+ val display = lastLayersSubject.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ lastLayersSubject.visibleRegion(FlickerComponentName.NAV_BAR)
+ .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtStart.kt
new file mode 100644
index 000000000..eba2fbe3a
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NavBarLayerPositionAtStart.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [FlickerComponentName.NAV_BAR] layer is placed at the correct position at the
+ * start of the transition
+ */
+class NavBarLayerPositionAtStart : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val firstLayersSubject = layerSubject.first()
+ val display = firstLayersSubject.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ firstLayersSubject.visibleRegion(FlickerComponentName.NAV_BAR)
+ .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowBecomesVisible.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowBecomesVisible.kt
new file mode 100644
index 000000000..3ec9cc741
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowBecomesVisible.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+open class NonAppWindowBecomesVisible(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isNonAppWindowInvisible(component)
+ .then()
+ .isAppWindowVisible(component)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsInvisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsInvisibleAlways.kt
new file mode 100644
index 000000000..2cbc23902
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsInvisibleAlways.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] window is invisible during the entire transition
+ */
+class NonAppWindowIsInvisibleAlways(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isNonAppWindowInvisible(component)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAlways.kt
new file mode 100644
index 000000000..649219369
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAlways.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] window is visible during the entire transition
+ */
+class NonAppWindowIsVisibleAlways(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isNonAppWindowVisible(component)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtEnd.kt
new file mode 100644
index 000000000..7471bc3fb
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtEnd.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] window is visible at the end of the transition
+ */
+class NonAppWindowIsVisibleAtEnd(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.last().isNonAppWindowVisible(component)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtStart.kt
new file mode 100644
index 000000000..c8f98c0d3
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/NonAppWindowIsVisibleAtStart.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [component] window is visible at the end of the transition
+ */
+class NonAppWindowIsVisibleAtStart(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.first().isNonAppWindowVisible(component)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/RotationLayerAppearsAndVanishes.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/RotationLayerAppearsAndVanishes.kt
new file mode 100644
index 000000000..1f1c1c79f
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/RotationLayerAppearsAndVanishes.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
+ * doesn't flicker, and disappears before the transition is complete.
+ */
+class RotationLayerAppearsAndVanishes : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val window = wmSubject.trace.entries.first().topVisibleAppWindow
+ val appComponent = FlickerComponentName.unflattenFromString(window)
+ layerSubject.isVisible(appComponent)
+ .then()
+ .isVisible(FlickerComponentName.ROTATION)
+ .then()
+ .isVisible(appComponent)
+ .isInvisible(FlickerComponentName.ROTATION)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtEnd.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtEnd.kt
new file mode 100644
index 000000000..d26d1c606
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtEnd.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [FlickerComponentName.STATUS_BAR] layer is placed at the correct position at the
+ * end of the transition
+ */
+class StatusBarLayerPositionAtEnd : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val endDisplay = layerSubject.last().entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("Display not found")
+
+ layerSubject.last().visibleRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(WindowUtils.getStatusBarPosition(endDisplay))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtStart.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtStart.kt
new file mode 100644
index 000000000..ad89b6a44
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/StatusBarLayerPositionAtStart.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks if the [FlickerComponentName.STATUS_BAR] layer is placed at the correct position at the
+ * start of the transition
+ */
+class StatusBarLayerPositionAtStart : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ val startDisplay = layerSubject.first().entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("Display not found")
+
+ layerSubject.first().visibleRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(WindowUtils.getStatusBarPosition(startDisplay))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleLayersShownMoreThanOneConsecutiveEntry.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleLayersShownMoreThanOneConsecutiveEntry.kt
new file mode 100644
index 000000000..57b39d919
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleLayersShownMoreThanOneConsecutiveEntry.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that all layers that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
+class VisibleLayersShownMoreThanOneConsecutiveEntry : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ layerSubject.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt
new file mode 100644
index 000000000..c029479fe
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.service.assertors.BaseAssertion
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that all windows that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
+class VisibleWindowsShownMoreThanOneConsecutiveEntry : BaseAssertion() {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesOutOfTop.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesOutOfTop.kt
new file mode 100644
index 000000000..b717e3f2c
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesOutOfTop.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that [component] starts on top and moves out of top during the transition
+ */
+open class WindowMovesOutOfTop(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isAppWindowOnTop(component)
+ .then()
+ .isAppWindowNotOnTop(component)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesToTop.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesToTop.kt
new file mode 100644
index 000000000..baf5ef2e7
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/assertors/common/WindowMovesToTop.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors.common
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.tags.Tag
+
+/**
+ * Checks that [component] starts not on top and moves to top during the transition
+ */
+open class WindowMovesToTop(windowName: String) : ComponentBaseTest(windowName) {
+ /** {@inheritDoc} */
+ override fun doEvaluate(
+ tag: Tag,
+ wmSubject: WindowManagerTraceSubject,
+ layerSubject: LayersTraceSubject
+ ) {
+ wmSubject.isAppWindowNotOnTop(component)
+ .then()
+ .isAppWindowOnTop(component)
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/config.json b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/config.json
new file mode 100644
index 000000000..4768e4523
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/config.json
@@ -0,0 +1,162 @@
+{
+ "assertors": [
+ {
+ "transition": "ROTATION",
+ "assertions": {
+ "presubmit": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsInvisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsInvisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.RotationLayerAppearsAndVanishes"
+ }
+ ],
+ "postsubmit": [],
+ "flaky": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtEnd",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAlways"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleWindowsShownMoreThanOneConsecutiveEntry"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleLayersShownMoreThanOneConsecutiveEntry"
+ }
+ ]
+ }
+ },
+ {
+ "transition": "APP_LAUNCH",
+ "assertions": {
+ "presubmit": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtEnd",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppLayerReplacesLauncher"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsInvisibleAtEnd",
+ "args": [
+ "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppLayerIsInvisibleAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppLayerIsVisibleAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppWindowReplacesLauncherAsTopWindow"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LauncherWindowMovesOutOfTop"
+ }
+ ],
+ "postsubmit": [],
+ "flaky": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.StatusBarLayerPositionAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.StatusBarLayerPositionAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAlways"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleWindowsShownMoreThanOneConsecutiveEntry"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleLayersShownMoreThanOneConsecutiveEntry"
+ }
+ ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerSubjectException.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerSubjectException.kt
index 7a5ea0c08..4f775ff1d 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerSubjectException.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerSubjectException.kt
@@ -17,13 +17,47 @@
package com.android.server.wm.flicker.traces
import com.android.server.wm.flicker.assertions.FlickerSubject
+import com.android.server.wm.traces.common.prettyTimestamp
/**
* Exception thrown by [FlickerSubject]s
*/
class FlickerSubjectException(
- flickerSubject: FlickerSubject,
+ internal val subject: FlickerSubject,
cause: Throwable
-) : AssertionError(flickerSubject.defaultFacts, cause) {
- internal val facts = flickerSubject.defaultFacts
+) : AssertionError(cause.message, if (cause is FlickerSubjectException) null else cause) {
+ internal val timestamp = subject.timestamp
+ private val prettyTimestamp =
+ if (timestamp > 0) "${prettyTimestamp(timestamp)} (timestamp=$timestamp)" else ""
+
+ internal val errorType: String =
+ if (cause is AssertionError) "Flicker assertion error" else "Unknown error"
+
+ internal val errorDescription = buildString {
+ appendln("Where? $prettyTimestamp")
+ val message = (cause.message ?: "").split(("\n"))
+ append("What? ")
+ if (message.size == 1) {
+ // Single line error message
+ appendln(message.first())
+ } else {
+ // Multi line error message
+ appendln()
+ message.forEach { appendln("\t$it") }
+ }
+ }
+
+ internal val subjectInformation = buildString {
+ appendln("Facts:")
+ subject.completeFacts.forEach { append("\t").appendln(it) }
+ }
+
+ override val message: String
+ get() = buildString {
+ appendln(errorType)
+ appendln()
+ append(errorDescription)
+ appendln()
+ append(subjectInformation)
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerTraceSubject.kt
index b6d260743..b5ad41944 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerTraceSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/FlickerTraceSubject.kt
@@ -19,6 +19,8 @@ package com.android.server.wm.flicker.traces
import com.android.server.wm.flicker.assertions.Assertion
import com.android.server.wm.flicker.assertions.AssertionsChecker
import com.android.server.wm.flicker.assertions.FlickerSubject
+import com.android.server.wm.traces.common.prettyTimestamp
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
/**
@@ -28,16 +30,37 @@ abstract class FlickerTraceSubject<EntrySubject : FlickerSubject>(
fm: FailureMetadata,
data: Any?
) : FlickerSubject(fm, data) {
+ override val timestamp: Long get() = subjects.first().timestamp
+ override val selfFacts by lazy {
+ val firstTimestamp = subjects.firstOrNull()?.timestamp ?: 0L
+ val lastTimestamp = subjects.lastOrNull()?.timestamp ?: 0L
+ val first = "${prettyTimestamp(firstTimestamp)} (timestamp=$firstTimestamp)"
+ val last = "${prettyTimestamp(lastTimestamp)} (timestamp=$lastTimestamp)"
+ listOf(Fact.fact("Trace start", first),
+ Fact.fact("Trace end", last))
+ }
+
protected val assertionsChecker = AssertionsChecker<EntrySubject>()
private var newAssertionBlock = true
abstract val subjects: List<EntrySubject>
- protected fun addAssertion(name: String, assertion: Assertion<EntrySubject>) {
+ /**
+ * Adds a new assertion block (if preceded by [then]) or appends an assertion to the
+ * latest existing assertion block
+ *
+ * @param name Assertion name
+ * @param isOptional If this assertion is optional or must pass
+ */
+ protected fun addAssertion(
+ name: String,
+ isOptional: Boolean = false,
+ assertion: Assertion<EntrySubject>
+ ) {
if (newAssertionBlock) {
- assertionsChecker.add(name, assertion)
+ assertionsChecker.add(name, isOptional, assertion)
} else {
- assertionsChecker.append(name, assertion)
+ assertionsChecker.append(name, isOptional, assertion)
}
newAssertionBlock = false
}
@@ -68,7 +91,30 @@ abstract class FlickerTraceSubject<EntrySubject : FlickerSubject>(
* Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
* after checkA passes.
*/
- protected fun startAssertionBlock() {
+ open fun then(): FlickerTraceSubject<EntrySubject> = apply {
+ startAssertionBlock()
+ }
+
+ /**
+ * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
+ * end of the trace without passing any assertion, return a failure with the name/reason from
+ * the first assertion
+ *
+ * @return
+ */
+ open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> =
+ apply { assertionsChecker.skipUntilFirstAssertion() }
+
+ /**
+ * Signal that the last assertion set is complete. The next assertion added will start a new
+ * set of assertions.
+ *
+ * E.g.: checkA().then().checkB()
+ *
+ * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
+ * after checkA passes.
+ */
+ private fun startAssertionBlock() {
newAssertionBlock = true
}
@@ -76,11 +122,14 @@ abstract class FlickerTraceSubject<EntrySubject : FlickerSubject>(
* Checks whether all the trace entries on the list are visible for more than one consecutive
* entry
*
- * @param [visibleEntries] a list of all the entries with their name and index
+ * @param [visibleEntriesProvider] a list of all the entries with their name and index
*/
protected fun visibleEntriesShownMoreThanOneConsecutiveTime(
visibleEntriesProvider: (EntrySubject) -> Set<String>
) {
+ if (subjects.isEmpty()) {
+ return
+ }
var lastVisible = visibleEntriesProvider(subjects.first())
val lastNew = lastVisible.toMutableSet()
@@ -102,4 +151,7 @@ abstract class FlickerTraceSubject<EntrySubject : FlickerSubject>(
lastEntry.fail("$lastNew is not visible for 2 entries")
}
}
+
+ override fun toString(): String = "${this::class.simpleName}" +
+ "(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})"
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/RegionSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/RegionSubject.kt
index 6c3f81b70..d10a9f11e 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/RegionSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/RegionSubject.kt
@@ -33,9 +33,10 @@ import com.google.common.truth.StandardSubjectBuilder
*/
class RegionSubject(
fm: FailureMetadata,
- private val subjects: List<FlickerSubject>,
+ override val parent: FlickerSubject?,
val region: android.graphics.Region
) : FlickerSubject(fm, region) {
+ override val timestamp: Long get() = parent?.timestamp ?: 0
private val topPositionSubject
get() = check(MSG_ERROR_TOP_POSITION).that(region.bounds.top)
private val bottomPositionSubject
@@ -50,15 +51,13 @@ class RegionSubject(
private val android.graphics.Rect.area get() = this.width() * this.height()
private val Rect.area get() = this.width * this.height
- override val defaultFacts: String = buildString {
- subjects.forEach { subject -> appendln(subject.defaultFacts) }
- }
+ override val selfFacts = listOf(Fact.fact("Region - Covered", region.toString()))
/**
* {@inheritDoc}
*/
override fun clone(): FlickerSubject {
- return RegionSubject(fm, subjects, region)
+ return RegionSubject(fm, parent, region)
}
/**
@@ -76,6 +75,34 @@ class RegionSubject(
}
/**
+ * Subtracts [other] from this subject [region]
+ */
+ fun minus(other: Region): RegionSubject = minus(other.toAndroidRegion())
+
+ /**
+ * Subtracts [other] from this subject [region]
+ */
+ fun minus(other: android.graphics.Region): RegionSubject {
+ val remainingRegion = android.graphics.Region(this.region)
+ remainingRegion.op(other, android.graphics.Region.Op.XOR)
+ return assertThat(remainingRegion, this)
+ }
+
+ /**
+ * Adds [other] to this subject [region]
+ */
+ fun plus(other: Region): RegionSubject = plus(other.toAndroidRegion())
+
+ /**
+ * Adds [other] to this subject [region]
+ */
+ fun plus(other: android.graphics.Region): RegionSubject {
+ val remainingRegion = android.graphics.Region(this.region)
+ remainingRegion.op(other, android.graphics.Region.Op.UNION)
+ return assertThat(remainingRegion, this)
+ }
+
+ /**
* Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller
* or equal to those of [region].
*
@@ -503,26 +530,24 @@ class RegionSubject(
* Boiler-plate Subject.Factory for RectSubject
*/
@JvmStatic
- @JvmOverloads
fun getFactory(
- flickerSubjects: List<FlickerSubject> = emptyList()
+ parent: FlickerSubject?
) = Factory { fm: FailureMetadata, region: android.graphics.Region? ->
val subjectRegion = region ?: android.graphics.Region()
- RegionSubject(fm, flickerSubjects, subjectRegion)
+ RegionSubject(fm, parent, subjectRegion)
}
/**
* User-defined entry point for existing android regions
*/
@JvmStatic
- @JvmOverloads
fun assertThat(
region: android.graphics.Region?,
- flickerSubjects: List<FlickerSubject> = emptyList()
+ parent: FlickerSubject? = null
): RegionSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(flickerSubjects))
+ .about(getFactory(parent))
.that(region ?: android.graphics.Region()) as RegionSubject
strategy.init(subject)
return subject
@@ -533,59 +558,47 @@ class RegionSubject(
*/
@JvmStatic
@JvmOverloads
- fun assertThat(
- rect: Array<Rect>,
- flickerSubjects: List<FlickerSubject> = emptyList()
- ): RegionSubject = assertThat(Region(rect), flickerSubjects)
+ fun assertThat(rect: Array<Rect>, parent: FlickerSubject? = null): RegionSubject =
+ assertThat(Region(rect), parent)
/**
* User-defined entry point for existing rects
*/
@JvmStatic
@JvmOverloads
- fun assertThat(
- rect: Rect?,
- flickerSubjects: List<FlickerSubject> = emptyList()
- ): RegionSubject = assertThat(Region(rect), flickerSubjects)
+ fun assertThat(rect: Rect?, parent: FlickerSubject? = null): RegionSubject =
+ assertThat(Region(rect), parent)
/**
* User-defined entry point for existing rects
*/
@JvmStatic
- fun assertThat(
- rect: RectF?,
- flickerSubjects: List<FlickerSubject> = emptyList()
- ): RegionSubject = assertThat(rect?.toRect(), flickerSubjects)
+ @JvmOverloads
+ fun assertThat(rect: RectF?, parent: FlickerSubject? = null): RegionSubject =
+ assertThat(rect?.toRect(), parent)
/**
* User-defined entry point for existing rects
*/
@JvmStatic
- fun assertThat(
- rect: Array<RectF>,
- flickerSubjects: List<FlickerSubject> = emptyList()
- ): RegionSubject = assertThat(
- mergeRegions(rect.map { Region(it.toRect()) }.toTypedArray()),
- flickerSubjects)
+ @JvmOverloads
+ fun assertThat(rect: Array<RectF>, parent: FlickerSubject? = null): RegionSubject =
+ assertThat(mergeRegions(rect.map { Region(it.toRect()) }.toTypedArray()), parent)
/**
* User-defined entry point for existing regions
*/
@JvmStatic
@JvmOverloads
- fun assertThat(
- regions: Array<Region>,
- flickerSubjects: List<FlickerSubject> = emptyList()
- ): RegionSubject = assertThat(mergeRegions(regions), flickerSubjects)
+ fun assertThat(regions: Array<Region>, parent: FlickerSubject? = null): RegionSubject =
+ assertThat(mergeRegions(regions), parent)
/**
* User-defined entry point for existing regions
*/
@JvmStatic
@JvmOverloads
- fun assertThat(
- region: Region?,
- flickerSubjects: List<FlickerSubject> = emptyList()
- ): RegionSubject = assertThat(region?.toAndroidRegion(), flickerSubjects)
+ fun assertThat(region: Region?, parent: FlickerSubject? = null): RegionSubject =
+ assertThat(region?.toAndroidRegion(), parent)
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/EventLogSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/EventLogSubject.kt
index 704e46b3e..8036730b8 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/EventLogSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/EventLogSubject.kt
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker.traces.eventlog
import com.android.server.wm.flicker.assertions.AssertionsChecker
import com.android.server.wm.flicker.assertions.FlickerSubject
+import com.android.server.wm.traces.common.prettyTimestamp
import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.Subject.Factory
@@ -30,10 +31,15 @@ class EventLogSubject private constructor(
failureMetadata: FailureMetadata,
private val trace: List<FocusEvent>
) : FlickerSubject(failureMetadata, trace) {
- override val defaultFacts: String by lazy {
- val first = subjects.first().defaultFacts
- val last = subjects.last().defaultFacts
- "EventLogSubject($first, $last)"
+ override val timestamp: Long get() = 0
+ override val parent: FlickerSubject? get() = null
+ override val selfFacts by lazy {
+ val firstTimestamp = subjects.first().timestamp
+ val lastTimestamp = subjects.last().timestamp
+ val first = "${prettyTimestamp(firstTimestamp)} (timestamp=$firstTimestamp)"
+ val last = "${prettyTimestamp(lastTimestamp)} (timestamp=$lastTimestamp)"
+ listOf(Fact.fact("Trace start", first),
+ Fact.fact("Trace end", last))
}
/** {@inheritDoc} */
@@ -52,7 +58,7 @@ class EventLogSubject private constructor(
focusList + trace.filter { it.hasFocus() }.map { it.window }
}
- fun focusChanges(windows: Array<out String>) = apply {
+ fun focusChanges(vararg windows: String) = apply {
if (windows.isNotEmpty()) {
val focusChanges = _focusChanges
.dropWhile { !it.contains(windows.first()) }
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/FocusEventSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/FocusEventSubject.kt
index fb6745dd5..1f691eb31 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/FocusEventSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/eventlog/FocusEventSubject.kt
@@ -18,19 +18,21 @@ package com.android.server.wm.flicker.traces.eventlog
import com.android.server.wm.flicker.assertions.FlickerSubject
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.StandardSubjectBuilder
class FocusEventSubject(
fm: FailureMetadata,
val event: FocusEvent,
- val trace: EventLogSubject?
+ override val parent: EventLogSubject?
) : FlickerSubject(fm, event) {
- override val defaultFacts by lazy { event.toString() }
+ override val timestamp: Long get() = 0
+ override val selfFacts by lazy { listOf(Fact.simpleFact(event.toString())) }
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return FocusEventSubject(fm, event, trace)
+ return FocusEventSubject(fm, event, parent)
}
fun hasFocus() {
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerSubject.kt
index efa0352b3..614cd9ae7 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerSubject.kt
@@ -20,8 +20,9 @@ import com.android.server.wm.flicker.assertions.Assertion
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.assertions.FlickerSubject
import com.android.server.wm.flicker.traces.RegionSubject
-import com.android.server.wm.traces.common.Bounds
+import com.android.server.wm.traces.common.Size
import com.android.server.wm.traces.common.layers.Layer
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.FailureStrategy
import com.google.common.truth.StandardSubjectBuilder
@@ -48,8 +49,9 @@ import com.google.common.truth.Subject.Factory
*/
class LayerSubject private constructor(
fm: FailureMetadata,
+ override val parent: FlickerSubject,
+ override val timestamp: Long,
val layer: Layer?,
- val entry: LayerTraceEntrySubject?,
private val layerName: String? = null
) : FlickerSubject(fm, layer) {
val isEmpty: Boolean get() = layer == null
@@ -62,16 +64,19 @@ class LayerSubject private constructor(
* Visible region calculated by the Composition Engine
*/
val visibleRegion: RegionSubject get() =
- RegionSubject.assertThat(layer?.visibleRegion, listOf(this))
+ RegionSubject.assertThat(layer?.visibleRegion, this)
/**
* Visible region calculated by the Composition Engine (when available) or calculated
* based on the layer bounds and transform
*/
val screenBounds: RegionSubject get() =
- RegionSubject.assertThat(layer?.screenBounds, listOf(this))
+ RegionSubject.assertThat(layer?.screenBounds, this)
- override val defaultFacts: String =
- "${entry?.defaultFacts ?: ""}\nFrame: ${layer?.currFrame}\nLayer: ${layer?.name}"
+ override val selfFacts = if (layer != null) {
+ listOf(Fact.fact("Frame", layer.currFrame), Fact.fact("Layer", layer.name))
+ } else {
+ listOf(Fact.fact("Layer name", layerName))
+ }
/**
* If the [layer] exists, executes a custom [assertion] on the current subject
@@ -83,7 +88,7 @@ class LayerSubject private constructor(
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return LayerSubject(fm, layer, entry, layerName)
+ return LayerSubject(fm, parent, timestamp, layer, layerName)
}
/**
@@ -102,7 +107,7 @@ class LayerSubject private constructor(
@Deprecated("Prefer hasBufferSize(bounds)")
fun hasBufferSize(size: Point): LayerSubject = apply {
- val bounds = Bounds(size.x, size.y)
+ val bounds = Size(size.x, size.y)
hasBufferSize(bounds)
}
@@ -112,9 +117,9 @@ class LayerSubject private constructor(
*
* @param size expected buffer size
*/
- fun hasBufferSize(size: Bounds): LayerSubject = apply {
+ fun hasBufferSize(size: Size): LayerSubject = apply {
layer ?: return exists()
- val bufferSize = layer.activeBuffer?.size ?: Bounds.EMPTY
+ val bufferSize = Size(layer.activeBuffer.width, layer.activeBuffer.height)
check("Incorrect buffer size").that(bufferSize).isEqualTo(size)
}
@@ -162,50 +167,43 @@ class LayerSubject private constructor(
* Boiler-plate Subject.Factory for LayerSubject
*/
@JvmStatic
- @JvmOverloads
- fun getFactory(entry: LayerTraceEntrySubject? = null) =
- Factory { fm: FailureMetadata, subject: Layer? -> LayerSubject(fm, subject, entry) }
+ fun getFactory(parent: FlickerSubject, timestamp: Long, name: String?) =
+ Factory { fm: FailureMetadata, subject: Layer? ->
+ LayerSubject(fm, parent, timestamp, subject, name)
+ }
/**
- * User-defined entry point for existing layers
+ * User-defined parent point for existing layers
*/
@JvmStatic
- @JvmOverloads
fun assertThat(
layer: Layer?,
- entry: LayerTraceEntrySubject? = null
+ parent: FlickerSubject,
+ timestamp: Long
): LayerSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(entry))
+ .about(getFactory(parent, timestamp, name = null))
.that(layer) as LayerSubject
strategy.init(subject)
return subject
}
/**
- * User-defined entry point for non existing layers
+ * User-defined parent point for non existing layers
*/
@JvmStatic
internal fun assertThat(
name: String,
- entry: LayerTraceEntrySubject?
+ parent: FlickerSubject,
+ timestamp: Long
): LayerSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(entry, name))
+ .about(getFactory(parent, timestamp, name))
.that(null) as LayerSubject
strategy.init(subject)
return subject
}
-
- /**
- * Boiler-plate Subject.Factory for LayerSubject
- */
- @JvmStatic
- internal fun getFactory(entry: LayerTraceEntrySubject?, name: String) =
- Factory { fm: FailureMetadata, subject: Layer? ->
- LayerSubject(fm, subject, entry, name)
- }
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceEntrySubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceEntrySubject.kt
index 7a902ae3f..8f3547785 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceEntrySubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayerTraceEntrySubject.kt
@@ -18,12 +18,13 @@ package com.android.server.wm.flicker.traces.layers
import com.android.server.wm.flicker.assertions.Assertion
import com.android.server.wm.flicker.assertions.FlickerSubject
-import com.android.server.wm.flicker.containsAny
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.traces.FlickerSubjectException
import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.layers.Layer
import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.LayersTrace
import com.google.common.truth.ExpectFailure
import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
@@ -56,12 +57,14 @@ import com.google.common.truth.Subject
class LayerTraceEntrySubject private constructor(
fm: FailureMetadata,
val entry: LayerTraceEntry,
- val trace: LayersTraceSubject?
+ val trace: LayersTrace?,
+ override val parent: FlickerSubject?
) : FlickerSubject(fm, entry) {
- override val defaultFacts: String = "${trace?.defaultFacts ?: ""}\nEntry: $entry"
+ override val timestamp: Long get() = entry.timestamp
+ override val selfFacts = listOf(Fact.fact("Entry", entry))
val subjects by lazy {
- entry.flattenedLayers.map { LayerSubject.assertThat(it, this) }
+ entry.flattenedLayers.map { LayerSubject.assertThat(it, this, timestamp) }
}
/**
@@ -73,15 +76,7 @@ class LayerTraceEntrySubject private constructor(
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return LayerTraceEntrySubject(fm, entry, trace)
- }
-
- /**
- * Asserts that current entry subject has an [LayerTraceEntry.timestamp] equals to
- * [timestamp]
- */
- fun hasTimestamp(timestamp: Long): LayerTraceEntrySubject = apply {
- check("Wrong entry timestamp").that(entry.timestamp).isEqualTo(timestamp)
+ return LayerTraceEntrySubject(fm, entry, trace, parent)
}
/**
@@ -103,112 +98,133 @@ class LayerTraceEntrySubject private constructor(
}
/**
- * Asserts that the current SurfaceFlinger state has [numberLayers] layers
- */
- fun hasLayersSize(numberLayers: Int): LayerTraceEntrySubject = apply {
- check("Wrong number of layers in entry")
- .that(entry.flattenedLayers.size)
- .isEqualTo(numberLayers)
- }
-
- /**
- * Obtains the region occupied by all layers with name containing any of [partialLayerNames]
+ * Obtains the region occupied by all layers with name containing [component]
*
- * @param partialLayerNames Name of the layer to search
+ * @param component Component to search
* @param useCompositionEngineRegionOnly If true, uses only the region calculated from the
* Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise calculates
* the visible region when the information is not available from the CE
*/
fun visibleRegion(
- vararg partialLayerNames: String,
+ component: FlickerComponentName? = null,
useCompositionEngineRegionOnly: Boolean = true
): RegionSubject {
+ val layerName = component?.toLayerName() ?: ""
val selectedLayers = subjects
- .filter { it.name.containsAny(*partialLayerNames) }
+ .filter { it.name.contains(layerName) }
if (selectedLayers.isEmpty()) {
- fail("Could not find", partialLayerNames.joinToString(", "))
+ fail(listOf(
+ Fact.fact(ASSERTION_TAG, "visibleRegion(${component?.toLayerName() ?: "<any>"})"),
+ Fact.fact("Use composition engine region", useCompositionEngineRegionOnly),
+ Fact.fact("Could not find", layerName))
+ )
}
val visibleLayers = selectedLayers.filter { it.isVisible }
return if (useCompositionEngineRegionOnly) {
val visibleAreas = visibleLayers.mapNotNull { it.layer?.visibleRegion }.toTypedArray()
- RegionSubject.assertThat(visibleAreas, selectedLayers)
+ RegionSubject.assertThat(visibleAreas, this)
} else {
val visibleAreas = visibleLayers.mapNotNull { it.layer?.screenBounds }.toTypedArray()
- RegionSubject.assertThat(visibleAreas, selectedLayers)
+ RegionSubject.assertThat(visibleAreas, this)
}
}
/**
- * Asserts that the SurfaceFlinger state contains a [Layer] with [Layer.name] containing any of
- * [partialLayerNames].
+ * Asserts the state contains a [Layer] with [Layer.name] containing [component].
*
- * @param partialLayerNames Name of the layers to search
+ * @param component Name of the layers to search
*/
- fun contains(vararg partialLayerNames: String): LayerTraceEntrySubject = apply {
- val found = entry.flattenedLayers.any { it.name.containsAny(*partialLayerNames) }
- if (partialLayerNames.isNotEmpty() && !found) {
- fail("Could not find", partialLayerNames.joinToString(", "))
+ fun contains(component: FlickerComponentName): LayerTraceEntrySubject = apply {
+ val layerName = component.toLayerName()
+ val found = entry.flattenedLayers.any { it.name.contains(layerName) }
+ if (!found) {
+ fail(Fact.fact(ASSERTION_TAG, "contains(${component.toLayerName()})"),
+ Fact.fact("Could not find", layerName))
}
}
/**
- * Asserts that the SurfaceFlinger state doesn't contain a [Layer] with [Layer.name] containing any of
+ * Asserts the state doesn't contain a [Layer] with [Layer.name] containing any of
*
- * @param partialLayerNames Name of the layers to search
+ * @param component Name of the layers to search
*/
- fun notContains(vararg partialLayerNames: String): LayerTraceEntrySubject = apply {
- val found = entry.flattenedLayers.none { it.name.containsAny(*partialLayerNames) }
- if (!found) {
- fail("Could find", partialLayerNames)
- }
+ fun notContains(component: FlickerComponentName): LayerTraceEntrySubject = apply {
+ val layerName = component.toLayerName()
+ val foundEntry = subjects.firstOrNull { it.name.contains(layerName) }
+ foundEntry?.fail(Fact.fact(ASSERTION_TAG, "notContains(${component.toLayerName()})"),
+ Fact.fact("Could find", foundEntry))
}
/**
- * Asserts that a [Layer] with [Layer.name] containing any of [partialLayerNames] is visible.
+ * Asserts that a [Layer] with [Layer.name] containing [component] is visible.
*
- * @param partialLayerNames Name of the layers to search
+ * @param component Name of the layers to search
*/
- fun isVisible(vararg partialLayerNames: String): LayerTraceEntrySubject = apply {
- contains(*partialLayerNames)
+ fun isVisible(component: FlickerComponentName): LayerTraceEntrySubject = apply {
+ contains(component)
+ var target: FlickerSubject? = null
var reason: Fact? = null
- val filteredLayers = entry.flattenedLayers
- .filter { it.name.containsAny(*partialLayerNames) }
+ val layerName = component.toLayerName()
+ val filteredLayers = subjects
+ .filter { it.name.contains(layerName) }
for (layer in filteredLayers) {
- if (layer.isHiddenByParent) {
- reason = Fact.fact("Hidden by parent", layer.parent.name)
+ if (layer.layer?.isHiddenByParent == true) {
+ reason = Fact.fact("Hidden by parent", layer.layer.parent?.name)
+ target = layer
continue
}
if (layer.isInvisible) {
- reason = Fact.fact("Is Invisible", layer.visibilityReason)
+ reason = Fact.fact("Is Invisible", layer.layer?.visibilityReason)
+ target = layer
continue
}
reason = null
+ target = null
break
}
- if (reason != null) {
- fail(reason)
+ reason?.run {
+ target?.fail(Fact.fact(ASSERTION_TAG, "isVisible(${component.toLayerName()})"), reason)
}
}
/**
- * Asserts that a [Layer] with [Layer.name] containing any of [partialLayerNames] doesn't exist or
+ * Asserts that a [Layer] with [Layer.name] containing [component] doesn't exist or
* is invisible.
*
- * @param partialLayerNames Name of the layers to search
+ * @param component Name of the layers to search
*/
- fun isInvisible(vararg partialLayerNames: String): LayerTraceEntrySubject = apply {
+ fun isInvisible(component: FlickerComponentName): LayerTraceEntrySubject = apply {
try {
- isVisible(*partialLayerNames)
+ isVisible(component)
} catch (e: FlickerSubjectException) {
val cause = e.cause
require(cause is AssertionError)
ExpectFailure.assertThat(cause).factKeys().isNotEmpty()
return@apply
}
- fail("Layer is visible", partialLayerNames)
+ val layerName = component.toLayerName()
+ val foundEntry = subjects
+ .firstOrNull { it.name.contains(layerName) && it.isVisible }
+ foundEntry?.fail(Fact.fact(ASSERTION_TAG, "isInvisible(${component.toLayerName()})"),
+ Fact.fact("Is visible", foundEntry))
+ }
+
+ /**
+ * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name]
+ * containing [component].
+ * Always returns a subject, event when the layer doesn't exist. To verify if layer
+ * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
+ *
+ * @return LayerSubject that can be used to make assertions on a single layer matching
+ */
+ fun layer(component: FlickerComponentName): LayerSubject {
+ val name = component.toLayerName()
+ return layer {
+ it.name.contains(name)
+ }
}
/**
@@ -222,10 +238,27 @@ class LayerTraceEntrySubject private constructor(
* [name] and [frameNumber].
*/
fun layer(name: String, frameNumber: Long): LayerSubject {
+ return layer(name) {
+ it.name.contains(name) && it.currFrame == frameNumber
+ }
+ }
+
+ /**
+ * Obtains a [LayerSubject] for the first occurrence of a [Layer] matching [predicate]
+ *
+ * Always returns a subject, event when the layer doesn't exist. To verify if layer
+ * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
+ *
+ * @param predicate to search for a layer
+ * @param name Name of the subject to use when not found (optional)
+ *
+ * @return [LayerSubject] that can be used to make assertions
+ */
+ @JvmOverloads
+ fun layer(name: String = "", predicate: (Layer) -> Boolean): LayerSubject {
return subjects.firstOrNull {
- it.layer?.name?.contains(name) == true &&
- it.layer.currFrame == frameNumber
- } ?: LayerSubject.assertThat(name, this)
+ it.layer?.run { predicate(this) } ?: false
+ } ?: LayerSubject.assertThat(name, this, timestamp)
}
override fun toString(): String {
@@ -237,26 +270,28 @@ class LayerTraceEntrySubject private constructor(
* Boiler-plate Subject.Factory for LayersTraceSubject
*/
private fun getFactory(
- trace: LayersTraceSubject? = null
+ trace: LayersTrace?,
+ parent: FlickerSubject?
): Factory<Subject, LayerTraceEntry> =
- Factory { fm, subject -> LayerTraceEntrySubject(fm, subject, trace) }
+ Factory { fm, subject -> LayerTraceEntrySubject(fm, subject, trace, parent) }
/**
* Creates a [LayerTraceEntrySubject] to representing a SurfaceFlinger state[entry],
* which can be used to make assertions.
*
* @param entry SurfaceFlinger trace entry
- * @param trace Trace that contains this entry (optional)
+ * @param parent Trace that contains this entry (optional)
*/
@JvmStatic
@JvmOverloads
fun assertThat(
entry: LayerTraceEntry,
- trace: LayersTraceSubject? = null
+ trace: LayersTrace? = null,
+ parent: FlickerSubject? = null
): LayerTraceEntrySubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(trace))
+ .about(getFactory(trace, parent))
.that(entry) as LayerTraceEntrySubject
strategy.init(subject)
return subject
@@ -267,8 +302,9 @@ class LayerTraceEntrySubject private constructor(
*/
@JvmStatic
@JvmOverloads
- fun entries(trace: LayersTraceSubject? = null): Factory<Subject, LayerTraceEntry> {
- return getFactory(trace)
- }
+ fun entries(
+ trace: LayersTrace? = null,
+ parent: FlickerSubject? = null
+ ): Factory<Subject, LayerTraceEntry> = getFactory(trace, parent)
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayersTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayersTraceSubject.kt
index 9c7ba2a03..39c8c6258 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayersTraceSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/layers/LayersTraceSubject.kt
@@ -22,9 +22,12 @@ import com.android.server.wm.flicker.assertions.Assertion
import com.android.server.wm.flicker.assertions.FlickerSubject
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.traces.FlickerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.layers.Layer
import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.traces.parser.toAndroidRect
+import com.android.server.wm.traces.parser.toAndroidRegion
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.FailureStrategy
import com.google.common.truth.StandardSubjectBuilder
@@ -56,20 +59,18 @@ import com.google.common.truth.Subject.Factory
*/
class LayersTraceSubject private constructor(
fm: FailureMetadata,
- val trace: LayersTrace
+ val trace: LayersTrace,
+ override val parent: LayersTraceSubject?
) : FlickerTraceSubject<LayerTraceEntrySubject>(fm, trace) {
- override val defaultFacts: String by lazy {
- buildString {
- if (trace.hasSource()) {
- append("Path: ${trace.source}")
- append("\n")
+ override val selfFacts
+ get() = super.selfFacts.toMutableList()
+ .also {
+ if (trace.hasSource()) {
+ it.add(Fact.fact("Trace file", trace.source))
+ }
}
- append("Trace: $trace")
- }
- }
-
override val subjects by lazy {
- trace.entries.map { LayerTraceEntrySubject.assertThat(it, this) }
+ trace.entries.map { LayerTraceEntrySubject.assertThat(it, trace, this) }
}
/**
@@ -81,21 +82,11 @@ class LayersTraceSubject private constructor(
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return LayersTraceSubject(fm, trace)
+ return LayersTraceSubject(fm, trace, parent)
}
- /**
- * Signal that the last assertion set is complete. The next assertion added will start a new
- * set of assertions.
- *
- * E.g.: checkA().then().checkB()
- *
- * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
- * after checkA passes.
- */
- fun then(): LayersTraceSubject = apply {
- startAssertionBlock()
- }
+ /** {@inheritDoc} */
+ override fun then(): LayersTraceSubject = apply { super.then() }
fun isEmpty(): LayersTraceSubject = apply {
check("Trace is empty").that(trace).isEmpty()
@@ -113,206 +104,243 @@ class LayersTraceSubject private constructor(
return subjects
.map { it.layer(name, frameNumber) }
.firstOrNull { it.isNotEmpty }
- ?: LayerSubject.assertThat(null)
+ ?: LayerSubject.assertThat(null, this, timestamp = subjects.first().entry.timestamp)
+ }
+
+ /**
+ * @return List of [LayerSubject]s matching [name] in the order they appear on the trace
+ */
+ fun layers(name: String): List<LayerSubject> {
+ return subjects
+ .map { it.layer { layer -> layer.name.contains(name) } }
+ .filter { it.isNotEmpty }
+ }
+
+ /**
+ * @return List of [LayerSubject]s matching [predicate] in the order they appear on the trace
+ */
+ fun layers(predicate: (Layer) -> Boolean): List<LayerSubject> {
+ return subjects
+ .map { it.layer { layer -> predicate(layer) } }
+ .filter { it.isNotEmpty }
}
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at least [testRegion], that is, if its area of the layer's visible
+ * [component] covers at least [testRegion], that is, if its area of the layer's visible
* region covers each point in the region.
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtLeast(
testRegion: Rect,
- vararg layerName: String
- ): LayersTraceSubject = this.coversAtLeast(testRegion, *layerName)
+ component: FlickerComponentName? = null
+ ): LayersTraceSubject = this.coversAtLeast(Region(testRegion), component)
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at least [testRegion], that is, if its area of the layer's visible
+ * [component] covers at least [testRegion], that is, if its area of the layer's visible
* region covers each point in the region.
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtLeast(
testRegion: com.android.server.wm.traces.common.Rect,
- vararg layerName: String
- ): LayersTraceSubject = this.coversAtLeast(testRegion, *layerName)
+ component: FlickerComponentName? = null
+ ): LayersTraceSubject = this.coversAtLeast(testRegion.toAndroidRect(), component)
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at most [testRegion], that is, if the area of any layer doesn't
+ * [component] covers at most [testRegion], that is, if the area of any layer doesn't
* cover any point outside of [testRegion].
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtMost(
testRegion: Rect,
- vararg layerName: String
- ): LayersTraceSubject = this.coversAtMost(testRegion, *layerName)
+ component: FlickerComponentName? = null
+ ): LayersTraceSubject = this.coversAtMost(Region(testRegion), component)
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at most [testRegion], that is, if the area of any layer doesn't
+ * [component] covers at most [testRegion], that is, if the area of any layer doesn't
* cover any point outside of [testRegion].
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtMost(
testRegion: com.android.server.wm.traces.common.Rect,
- vararg layerName: String
- ): LayersTraceSubject = this.coversAtMost(testRegion, *layerName)
+ component: FlickerComponentName? = null
+ ): LayersTraceSubject = this.coversAtMost(testRegion.toAndroidRect(), component)
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at least [testRegion], that is, if its area of the layer's visible
+ * [component] covers at least [testRegion], that is, if its area of the layer's visible
* region covers each point in the region.
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtLeast(
testRegion: Region,
- vararg layerName: String
+ component: FlickerComponentName? = null
): LayersTraceSubject = apply {
- addAssertion("coversAtLeast($testRegion, ${layerName.joinToString(", ")})") {
- it.visibleRegion(*layerName).coversAtLeast(testRegion)
+ addAssertion("coversAtLeast($testRegion, ${component?.toLayerName()})") {
+ it.visibleRegion(component).coversAtLeast(testRegion)
}
}
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at least [testRegion], that is, if its area of the layer's visible
+ * [component] covers at least [testRegion], that is, if its area of the layer's visible
* region covers each point in the region.
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtLeast(
testRegion: com.android.server.wm.traces.common.Region,
- vararg layerName: String
- ): LayersTraceSubject = apply {
- addAssertion("coversAtLeast($testRegion, ${layerName.joinToString(", ")})") {
- it.visibleRegion(*layerName).coversAtLeast(testRegion)
- }
- }
+ component: FlickerComponentName? = null
+ ): LayersTraceSubject = this.coversAtLeast(testRegion.toAndroidRegion(), component)
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at most [testRegion], that is, if the area of any layer doesn't
+ * [component] covers at most [testRegion], that is, if the area of any layer doesn't
* cover any point outside of [testRegion].
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtMost(
testRegion: Region,
- vararg layerName: String
+ component: FlickerComponentName? = null
): LayersTraceSubject = apply {
- addAssertion("coversAtMost($testRegion, ${layerName.joinToString(", ")}") {
- it.visibleRegion(*layerName).coversAtMost(testRegion)
+ addAssertion("coversAtMost($testRegion, ${component?.toLayerName()}") {
+ it.visibleRegion(component).coversAtMost(testRegion)
}
}
/**
* Asserts that the visible area covered by any [Layer] with [Layer.name] containing any of
- * [layerName] covers at most [testRegion], that is, if the area of any layer doesn't
+ * [component] covers at most [testRegion], that is, if the area of any layer doesn't
* cover any point outside of [testRegion].
*
* @param testRegion Expected covered area
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
+ @JvmOverloads
fun coversAtMost(
testRegion: com.android.server.wm.traces.common.Region,
- vararg layerName: String
- ): LayersTraceSubject = apply {
- addAssertion("coversAtMost($testRegion, ${layerName.joinToString(", ")}") {
- it.visibleRegion(*layerName).coversAtMost(testRegion)
- }
- }
+ component: FlickerComponentName? = null
+ ): LayersTraceSubject = this.coversAtMost(testRegion.toAndroidRegion(), component)
/**
* Checks that all visible layers are shown for more than one consecutive entry
*/
@JvmOverloads
fun visibleLayersShownMoreThanOneConsecutiveEntry(
- ignoreLayers: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ ignoreLayers: List<FlickerComponentName> = listOf(FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
): LayersTraceSubject = apply {
visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
subject.entry.visibleLayers
- .filter { ignoreLayers.none { layerName -> layerName in it.name } }
+ .filter { ignoreLayers.none { component -> component.toLayerName() in it.name } }
.map { it.name }
.toSet()
}
}
/**
- * Asserts that a [Layer] with [Layer.name] containing any of [layerName] has a visible region
+ * Asserts that a [Layer] with [Layer.name] containing any of [component] has a visible region
* of exactly [expectedVisibleRegion] in trace entries.
*
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
* @param expectedVisibleRegion Expected visible region of the layer
*/
+ @JvmOverloads
fun coversExactly(
expectedVisibleRegion: Region,
- vararg layerName: String
+ component: FlickerComponentName? = null
): LayersTraceSubject = apply {
- addAssertion("coversExactly(${layerName.joinToString(", ")}$expectedVisibleRegion)") {
- it.visibleRegion(*layerName).coversExactly(expectedVisibleRegion)
+ addAssertion("coversExactly($component$expectedVisibleRegion)") {
+ it.visibleRegion(component).coversExactly(expectedVisibleRegion)
}
}
/**
* Asserts that each entry in the trace doesn't contain a [Layer] with [Layer.name]
- * containing [layerName].
+ * containing [component].
*
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun notContains(vararg layerName: String): LayersTraceSubject =
+ @JvmOverloads
+ fun notContains(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): LayersTraceSubject =
apply {
- addAssertion("notContains(${layerName.joinToString(", ")})") {
- it.notContains(*layerName)
+ addAssertion("notContains(${component.toLayerName()})", isOptional) {
+ it.notContains(component)
}
}
/**
* Asserts that each entry in the trace contains a [Layer] with [Layer.name] containing any of
- * [layerName].
+ * [component].
*
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun contains(vararg layerName: String): LayersTraceSubject =
- apply { addAssertion("contains(${layerName.joinToString(", ")})") {
- it.contains(*layerName) }
+ @JvmOverloads
+ fun contains(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): LayersTraceSubject =
+ apply { addAssertion("contains(${component.toLayerName()})", isOptional) {
+ it.contains(component) }
}
/**
* Asserts that each entry in the trace contains a [Layer] with [Layer.name] containing any of
- * [layerName] that is visible.
+ * [component] that is visible.
*
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
- fun isVisible(vararg layerName: String): LayersTraceSubject =
- apply { addAssertion("isVisible(${layerName.joinToString(", ")})") {
- it.isVisible(*layerName) }
+ @JvmOverloads
+ fun isVisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): LayersTraceSubject =
+ apply { addAssertion("isVisible(${component.toLayerName()})", isOptional) {
+ it.isVisible(component) }
}
/**
* Asserts that each entry in the trace doesn't contain a [Layer] with [Layer.name]
- * containing [layerName] or that the layer is not visible .
+ * containing [component] or that the layer is not visible .
*
- * @param layerName Name of the layer to search
+ * @param component Name of the layer to search
*/
- fun isInvisible(vararg layerName: String): LayersTraceSubject =
+ @JvmOverloads
+ fun isInvisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): LayersTraceSubject =
apply {
- addAssertion("hidesLayer(${layerName.joinToString(", ")})") {
- it.isInvisible(*layerName)
+ addAssertion("isInvisible(${component.toLayerName()})", isOptional) {
+ it.isInvisible(component)
}
}
@@ -321,8 +349,9 @@ class LayersTraceSubject private constructor(
*/
operator fun invoke(
name: String,
+ isOptional: Boolean = false,
assertion: Assertion<LayerTraceEntrySubject>
- ): LayersTraceSubject = apply { addAssertion(name, assertion) }
+ ): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) }
fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject = apply {
val firstFrame = frameNumbers.first()
@@ -370,8 +399,8 @@ class LayersTraceSubject private constructor(
/**
* Boiler-plate Subject.Factory for LayersTraceSubject
*/
- private val FACTORY: Factory<Subject, LayersTrace> =
- Factory { fm, subject -> LayersTraceSubject(fm, subject) }
+ private fun getFactory(parent: LayersTraceSubject?): Factory<Subject, LayersTrace> =
+ Factory { fm, subject -> LayersTraceSubject(fm, subject, parent) }
/**
* Creates a [LayersTraceSubject] to representing a SurfaceFlinger trace,
@@ -380,10 +409,11 @@ class LayersTraceSubject private constructor(
* @param trace SurfaceFlinger trace
*/
@JvmStatic
- fun assertThat(trace: LayersTrace): LayersTraceSubject {
+ @JvmOverloads
+ fun assertThat(trace: LayersTrace, parent: LayersTraceSubject? = null): LayersTraceSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(FACTORY)
+ .about(getFactory(parent))
.that(trace) as LayersTraceSubject
strategy.init(subject)
return subject
@@ -393,8 +423,8 @@ class LayersTraceSubject private constructor(
* Static method for getting the subject factory (for use with assertAbout())
*/
@JvmStatic
- fun entries(): Factory<Subject, LayersTrace> {
- return FACTORY
+ fun entries(parent: LayersTraceSubject?): Factory<Subject, LayersTrace> {
+ return getFactory(parent)
}
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerStateSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerStateSubject.kt
index ea642ec2a..b7a55dde8 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerStateSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerStateSubject.kt
@@ -16,19 +16,18 @@
package com.android.server.wm.flicker.traces.windowmanager
-import android.content.ComponentName
import android.view.Display
+import androidx.annotation.VisibleForTesting
import com.android.server.wm.flicker.assertions.Assertion
import com.android.server.wm.flicker.assertions.FlickerSubject
-import com.android.server.wm.flicker.containsAny
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.Region
import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.common.windowmanager.windows.Activity
import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import com.android.server.wm.traces.parser.toActivityName
import com.android.server.wm.traces.parser.toAndroidRegion
-import com.android.server.wm.traces.parser.toWindowName
import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.FailureStrategy
@@ -61,14 +60,31 @@ import com.google.common.truth.Subject.Factory
class WindowManagerStateSubject private constructor(
fm: FailureMetadata,
val wmState: WindowManagerState,
- val trace: WindowManagerTraceSubject?
+ val trace: WindowManagerTraceSubject?,
+ override val parent: FlickerSubject?
) : FlickerSubject(fm, wmState) {
- override val defaultFacts = "${trace?.defaultFacts ?: ""}\nEntry: $wmState"
+ override val timestamp: Long get() = wmState.timestamp
+ override val selfFacts = listOf(Fact.fact("Entry", wmState))
val subjects by lazy {
- wmState.windowStates.map { WindowStateSubject.assertThat(it, this) }
+ wmState.windowStates.map { WindowStateSubject.assertThat(it, this, timestamp) }
}
+ val appWindows: List<WindowStateSubject>
+ get() = subjects.filter { wmState.appWindows.contains(it.windowState) }
+
+ val nonAppWindows: List<WindowStateSubject>
+ get() = subjects.filter { wmState.nonAppWindows.contains(it.windowState) }
+
+ val aboveAppWindows: List<WindowStateSubject>
+ get() = subjects.filter { wmState.aboveAppWindows.contains(it.windowState) }
+
+ val belowAppWindows: List<WindowStateSubject>
+ get() = subjects.filter { wmState.belowAppWindows.contains(it.windowState) }
+
+ val visibleWindows: List<WindowStateSubject>
+ get() = subjects.filter { wmState.visibleWindows.contains(it.windowState) }
+
/**
* Executes a custom [assertion] on the current subject
*/
@@ -77,265 +93,188 @@ class WindowManagerStateSubject private constructor(
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return WindowManagerStateSubject(fm, wmState, trace)
+ return WindowManagerStateSubject(fm, wmState, trace, parent)
}
/**
- * Asserts that the current WindowManager state doesn't contain [WindowState]s
+ * Asserts the current WindowManager state doesn't contain [WindowState]s
*/
fun isEmpty(): WindowManagerStateSubject = apply {
- check("State is empty")
- .that(wmState.windowStates)
- .isEmpty()
+ check("State is empty").that(subjects).isEmpty()
}
/**
- * Asserts that the current WindowManager state contains [WindowState]s
+ * Asserts the current WindowManager state contains [WindowState]s
*/
fun isNotEmpty(): WindowManagerStateSubject = apply {
- check("State is not empty")
- .that(wmState.windowStates)
- .isNotEmpty()
+ check("State is not empty").that(subjects).isNotEmpty()
}
/**
- * Obtains the region occupied by all windows with name containing any of [partialWindowTitles]
+ * Obtains the region occupied by all windows with name containing any of [component]
*
- * @param partialWindowTitles Name of the layer to search
+ * @param component Component to search
*/
- fun frameRegion(vararg partialWindowTitles: String): RegionSubject {
- val selectedWindows = subjects.filter { it.name.containsAny(*partialWindowTitles) }
+ fun frameRegion(component: FlickerComponentName?): RegionSubject {
+ val windowName = component?.toWindowName() ?: ""
+ val selectedWindows = subjects.filter { it.name.contains(windowName) }
if (selectedWindows.isEmpty()) {
- fail("Could not find", selectedWindows.joinToString(", "))
+ fail(Fact.fact(ASSERTION_TAG, "frameRegion(${component?.toWindowName() ?: "<any>"})"),
+ Fact.fact("Could not find", windowName))
}
val visibleWindows = selectedWindows.filter { it.isVisible }
val frameRegions = visibleWindows.mapNotNull { it.windowState?.frameRegion }.toTypedArray()
- return RegionSubject.assertThat(frameRegions, selectedWindows)
- }
-
- /**
- * Asserts that the WindowManager state contains a [WindowState] with [WindowState.title]
- * containing any of [partialWindowTitles].
- *
- * @param partialWindowTitles window titles to search to search
- */
- fun contains(vararg partialWindowTitles: String): WindowManagerStateSubject = apply {
- val found = if (partialWindowTitles.isNotEmpty()) {
- wmState.windowStates.any { it.name.containsAny(*partialWindowTitles) }
- } else {
- wmState.windowStates.isNotEmpty()
- }
-
- if (!found) {
- fail("Could not find", partialWindowTitles.joinToString(", "))
- }
- }
-
- /**
- * Asserts that the WindowManager state doesn't contain a [WindowState] with
- * [WindowState.title] containing [partialWindowTitles].
- *
- * @param partialWindowTitles Title of the window to search
- */
- fun notContains(vararg partialWindowTitles: String): WindowManagerStateSubject = apply {
- val found = wmState.windowStates.none { it.name.containsAny(*partialWindowTitles) }
- if (!found) {
- fail("Could find", partialWindowTitles.joinToString(", "))
- }
- }
-
- /**
- * Asserts that a [WindowState] with [WindowState.title] containing [partialWindowTitles] is visible.
- *
- * @param partialWindowTitles Title of the window to search
- */
- fun isVisible(vararg partialWindowTitles: String): WindowManagerStateSubject = apply {
- wmState.windowStates.checkVisibility(*partialWindowTitles, isVisible = true)
- }
-
- /**
- * Asserts that a [WindowState] with [WindowState.title] containing [partialWindowTitles] doesn't
- * exist or is invisible.
- *
- * @param partialWindowTitles Title of the window to search
- */
- fun isInvisible(vararg partialWindowTitles: String): WindowManagerStateSubject = apply {
- wmState.windowStates.checkVisibility(*partialWindowTitles, isVisible = false)
- }
-
- private fun Array<WindowState>.checkIsVisible(vararg partialWindowTitles: String) {
- this@WindowManagerStateSubject.contains(*partialWindowTitles)
- val visibleWindows = this.filter { it.isVisible }
- .filter { it.name.containsAny(*partialWindowTitles) }
-
- if (visibleWindows.isEmpty()) {
- fail("Is Invisible", partialWindowTitles.joinToString(", "))
- }
- }
-
- private fun Array<WindowState>.checkIsInvisible(vararg partialWindowTitles: String) {
- try {
- notContains(*partialWindowTitles)
- } catch (e: AssertionError) {
- val invisibleWindows = this.filterNot { it.isVisible }
- .filter { it.name.containsAny(*partialWindowTitles) }
- if (invisibleWindows.isEmpty()) {
- fail("Is Visible", partialWindowTitles.joinToString(", "))
- }
- }
- }
-
- private fun Array<WindowState>.checkVisibility(
- vararg partialWindowTitles: String,
- isVisible: Boolean
- ) {
- if (isVisible) {
- checkIsVisible(*partialWindowTitles)
- } else {
- checkIsInvisible(*partialWindowTitles)
- }
+ return RegionSubject.assertThat(frameRegions, this)
}
/**
- * Asserts that the non-app window ([WindowManagerState.nonAppWindows]) with title
- * containing [partialWindowTitles] exists, is above all app windows ([WindowManagerState.appWindows])
- * and has a visibility equal to [isVisible]
- *
- * This assertion can be used, for example, to assert that the Status and Navigation bars
- * are visible and shown above the app
+ * Asserts the state contains a [WindowState] with title matching [component] above the
+ * app windows
*
- * @param partialWindowTitles window title to search
- * @param isVisible if the found window should be visible or not
+ * @param component Component to search
*/
- @JvmOverloads
- fun isAboveAppWindow(
- vararg partialWindowTitles: String,
- isVisible: Boolean = true
- ): WindowManagerStateSubject = apply {
- wmState.aboveAppWindows.checkVisibility(*partialWindowTitles, isVisible = isVisible)
+ fun containsAboveAppWindow(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ contains(aboveAppWindows, component)
}
/**
- * Asserts that the non-app window ([WindowManagerState.nonAppWindows]) with title
- * containing [partialWindowTitles] exists, is below all app windows ([WindowManagerState.appWindows])
- * and has a visibility equal to [isVisible]
+ * Asserts the state contains a [WindowState] with title matching [component] below the
+ * app windows
*
- * This assertion can be used, for example, to assert that the wallpaper is visible and
- * shown below the app
- *
- * @param partialWindowTitles window title to search
- * @param isVisible if the found window should be visible or not
+ * @param component Component to search
*/
- @JvmOverloads
- fun isBelowAppWindow(
- vararg partialWindowTitles: String,
- isVisible: Boolean = true
- ): WindowManagerStateSubject = apply {
- wmState.belowAppWindows.checkVisibility(*partialWindowTitles, isVisible = isVisible)
+ fun containsBelowAppWindow(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ contains(belowAppWindows, component)
}
/**
- * Asserts that a window A with title containing [aboveWindowTitle] exists,
- * a window B with title containing [belowWindowTitle] also exists, and that
- * A is shown above B.
+ * Asserts the state contains [WindowState]s with titles matching [aboveWindowComponent] and
+ * [belowWindowComponent], and that [aboveWindowComponent] is above [belowWindowComponent]
*
* This assertion can be used, for example, to assert that a PIP window is shown above
* other apps.
*
- * @param aboveWindowTitle name of the window that should be above
- * @param belowWindowTitle name of the window that should be below
+ * @param aboveWindowComponent name of the window that should be above
+ * @param belowWindowComponent name of the window that should be below
*/
- fun isAboveWindow(aboveWindowTitle: String, belowWindowTitle: String) {
+ fun isAboveWindow(
+ aboveWindowComponent: FlickerComponentName,
+ belowWindowComponent: FlickerComponentName
+ ): WindowManagerStateSubject = apply {
+ contains(aboveWindowComponent)
+ contains(belowWindowComponent)
+
// windows are ordered by z-order, from top to bottom
+ val aboveWindowTitle = aboveWindowComponent.toWindowName()
+ val belowWindowTitle = belowWindowComponent.toWindowName()
val aboveZ = wmState.windowStates.indexOfFirst { aboveWindowTitle in it.name }
val belowZ = wmState.windowStates.indexOfFirst { belowWindowTitle in it.name }
-
- contains(aboveWindowTitle)
- contains(belowWindowTitle)
if (aboveZ >= belowZ) {
- fail("$aboveWindowTitle is above $belowWindowTitle")
+ val aboveWindow = subjects.first { aboveWindowTitle in it.name }
+ aboveWindow.fail(Fact.fact(ASSERTION_TAG, "isAboveWindow(above=$aboveWindowTitle, " +
+ "below=$belowWindowTitle"),
+ Fact.fact("Above", aboveWindowTitle),
+ Fact.fact("Below", belowWindowTitle))
}
}
/**
- * Asserts that the WindowManager state contains a non-app [WindowState] with
- * [WindowState.title] containing [partialWindowTitles] and that its visibility is
- * equal to [isVisible]
+ * Asserts the state contains a non-app [WindowState] with title matching [component]
*
- * @param partialWindowTitles window title to search
- * @param isVisible if the found window should be visible or not
+ * @param component Component to search
*/
- @JvmOverloads
- fun containsNonAppWindow(
- vararg partialWindowTitles: String,
- isVisible: Boolean = true
- ): WindowManagerStateSubject = apply {
- wmState.nonAppWindows.checkVisibility(*partialWindowTitles, isVisible = isVisible)
+ fun containsNonAppWindow(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ contains(nonAppWindows, component)
}
/**
- * Asserts that the title of the top visible app window in the state contains any
- * of [partialWindowTitles]
+ * Asserts the title of the top visible app window in the state contains [component]
*
- * @param partialWindowTitles window title to search
- */
- fun showsAppWindowOnTop(vararg partialWindowTitles: String): WindowManagerStateSubject = apply {
- contains(*partialWindowTitles)
- val windowOnTop = wmState.topVisibleAppWindow.containsAny(*partialWindowTitles)
+ * @param component Component to search
+ */
+ fun isAppWindowOnTop(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ val windowName = component.toWindowName()
+ if (!wmState.topVisibleAppWindow.contains(windowName)) {
+ val topWindow = subjects.first { it.name == wmState.topVisibleAppWindow }
+ topWindow.fail(
+ Fact.fact(ASSERTION_TAG, "isAppWindowOnTop(${component.toWindowName()})"),
+ Fact.fact("Not on top", component.toWindowName()),
+ Fact.fact("Found", wmState.topVisibleAppWindow)
+ )
+ }
+ }
- if (!windowOnTop) {
- fail(Fact.fact("Not on top", partialWindowTitles.joinToString(", ")),
- Fact.fact("Found", wmState.topVisibleAppWindow))
+ /**
+ * Asserts the title of the top visible app window in the state contains [component]
+ *
+ * @param component Component to search
+ */
+ fun isAppWindowNotOnTop(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ val windowName = component.toWindowName()
+ if (wmState.topVisibleAppWindow.contains(windowName)) {
+ val topWindow = subjects.first { it.name == wmState.topVisibleAppWindow }
+ topWindow.fail(
+ Fact.fact(ASSERTION_TAG, "isAppWindowNotOnTop(${component.toWindowName()})"),
+ Fact.fact("On top", component.toWindowName())
+ )
}
}
/**
- * Asserts that the [WindowState.bounds] of the [WindowState] with [WindowState.title]
- * contained in any of [partialWindowTitles] don't overlap.
+ * Asserts the bounds of the [WindowState]s title matching [component] don't overlap.
*
- * @param partialWindowTitles Title of the windows that should not overlap
+ * @param component Component to search
*/
- fun noWindowsOverlap(vararg partialWindowTitles: String): WindowManagerStateSubject = apply {
- partialWindowTitles.forEach { contains(it) }
- val foundWindows = partialWindowTitles.toSet()
- .associateWith { title -> wmState.windowStates.find { it.name.contains(title) } }
+ fun doNotOverlap(
+ vararg component: FlickerComponentName
+ ): WindowManagerStateSubject = apply {
+ component.forEach { contains(it) }
+ val foundWindows = component.toSet()
+ .associateWith { act ->
+ wmState.windowStates.firstOrNull { it.name.contains(act.toWindowName()) }
+ }
// keep entries only for windows that we actually found by removing nulls
.filterValues { it != null }
- .mapValues { (_, v) -> v!!.frameRegion }
+ val foundWindowsRegions = foundWindows
+ .mapValues { (_, v) -> v?.frameRegion ?: Region.EMPTY }
- val regions = foundWindows.entries.toList()
+ val regions = foundWindowsRegions.entries.toList()
for (i in regions.indices) {
val (ourTitle, ourRegion) = regions[i]
for (j in i + 1 until regions.size) {
val (otherTitle, otherRegion) = regions[j]
if (ourRegion.toAndroidRegion().op(otherRegion.toAndroidRegion(),
android.graphics.Region.Op.INTERSECT)) {
- fail(Fact.fact("Overlap", ourTitle), Fact.fact("Overlap", otherTitle))
+ val window = foundWindows[ourTitle] ?: error("Window $ourTitle not found")
+ val windowSubject = subjects.first { it.windowState == window }
+ windowSubject.fail(Fact.fact(ASSERTION_TAG,
+ "noWindowsOverlap${component.joinToString { it.toWindowName() }}"),
+ Fact.fact("Overlap", ourTitle),
+ Fact.fact("Overlap", otherTitle))
}
}
}
}
/**
- * Asserts that the WindowManager state contains an app [WindowState] with
- * [WindowState.title] containing [partialWindowTitles] and that its visibility
- * is equal to [isVisible]
+ * Asserts the state contains an app [WindowState] with title matching [component]
*
- * @param partialWindowTitles window title to search
- * @param isVisible if the found window should be visible or not
+ * @param component Component to search
*/
- @JvmOverloads
- fun containsAppWindow(
- vararg partialWindowTitles: String,
- isVisible: Boolean = true
- ): WindowManagerStateSubject = apply {
- wmState.appWindows.checkVisibility(*partialWindowTitles, isVisible = isVisible)
+ fun containsAppWindow(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ val windowName = component.toWindowName()
+ // Check existence of activity
+ val activity = wmState.getActivitiesForWindow(windowName).firstOrNull()
+ check("Activity for window $windowName must exist.")
+ .that(activity).isNotNull()
+ // Check existence of window.
+ contains(component)
}
/**
- * Asserts that the display with id [displayId] has rotation [rotation]
+ * Asserts the display with id [displayId] has rotation [rotation]
*
* @param rotation to assert
* @param displayId of the target display
@@ -351,72 +290,69 @@ class WindowManagerStateSubject private constructor(
}
/**
- * Asserts that the display with id [displayId] has rotation [rotation]
+ * Asserts the state contains a [WindowState] with title matching [component].
*
- * @param rotation to assert
- * @param displayId of the target display
+ * @param component Component name to search
*/
- @JvmOverloads
- fun isNotRotation(
- rotation: Int,
- displayId: Int = Display.DEFAULT_DISPLAY
- ): WindowManagerStateSubject = apply {
- check("Rotation should not be $rotation")
- .that(rotation)
- .isNotEqualTo(wmState.getRotation(displayId))
+ fun contains(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ contains(subjects, component)
}
/**
- * Asserts that the WindowManager state contains a [WindowState] with [WindowState.title]
- * equal to [ComponentName.toWindowName] and an [Activity] with [Activity.title] equal to
- * [ComponentName.toActivityName]
+ * Asserts the state doesn't contain a [WindowState] nor an [Activity] with title
+ * matching [component].
*
- * @param activity Component name to search
+ * @param component Component name to search
*/
- fun contains(activity: ComponentName): WindowManagerStateSubject = apply {
- val windowName = activity.toWindowName()
- val activityName = activity.toActivityName()
- check("Activity=$activityName must exist.")
- .that(wmState.containsActivity(activityName)).isTrue()
- check("Window=$windowName must exits.")
- .that(wmState.containsWindow(windowName)).isTrue()
+ fun notContainsAppWindow(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ val activityName = component.toActivityName()
+ // system components (e.g., NavBar, StatusBar, PipOverlay) don't have a package name
+ // nor an activity, ignore them
+ check("Activity=$activityName must NOT exist.")
+ .that(wmState.containsActivity(activityName)).isFalse()
+ notContains(component)
}
/**
- * Asserts that the WindowManager state doesn't contain a [WindowState] with [WindowState.title]
- * equal to [ComponentName.toWindowName] nor an [Activity] with [Activity.title] equal to
- * [ComponentName.toActivityName]
+ * Asserts the state doesn't contain a [WindowState] with title matching [component].
*
- * @param activity Component name to search
+ * @param component Component name to search
*/
- fun notContains(activity: ComponentName): WindowManagerStateSubject = apply {
- val windowName = activity.toWindowName()
- val activityName = activity.toActivityName()
- check("Activity=$activityName must NOT exist.")
- .that(wmState.containsActivity(activityName)).isFalse()
+ fun notContains(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ val windowName = component.toWindowName()
check("Window=$windowName must NOT exits.")
.that(wmState.containsWindow(windowName)).isFalse()
}
- @JvmOverloads
- fun isRecentsActivityVisible(visible: Boolean = true): WindowManagerStateSubject = apply {
+ fun isRecentsActivityVisible(): WindowManagerStateSubject = apply {
if (wmState.isHomeRecentsComponent) {
isHomeActivityVisible()
} else {
- check("Recents activity is ${if (visible) "" else "not"} visible")
+ check("Recents activity visibility")
.that(wmState.isRecentsActivityVisible)
- .isEqualTo(visible)
+ .isTrue()
+ }
+ }
+
+ fun isRecentsActivityInvisible(): WindowManagerStateSubject = apply {
+ if (wmState.isHomeRecentsComponent) {
+ isHomeActivityInvisible()
+ } else {
+ check("Recents activity visibility")
+ .that(wmState.isRecentsActivityVisible)
+ .isFalse()
}
}
/**
- * Asserts that the WindowManager state is valid, that is, if it has:
+ * Asserts the state is valid, that is, if it has:
* - a resumed activity
* - a focused activity
* - a focused window
* - a front window
* - a focused app
*/
+ @VisibleForTesting
fun isValid(): WindowManagerStateSubject = apply {
check("Must have stacks").that(wmState.stackCount).isGreaterThan(0)
// TODO: Update when keyguard will be shown on multiple displays
@@ -442,229 +378,171 @@ class WindowManagerStateSubject private constructor(
}
/**
- * Asserts that the [WindowManagerState.focusedActivity] and [WindowManagerState.focusedApp]
- * match [activity]
+ * Asserts the state contains a visible window with [WindowState.title] matching [component].
*
- * @param activity Component name to search
- */
- fun hasFocusedActivity(activity: ComponentName): WindowManagerStateSubject = apply {
- val activityComponentName = activity.toActivityName()
- check("Focused activity invalid")
- .that(activityComponentName)
- .isEqualTo(wmState.focusedActivity)
- check("Focused app invalid")
- .that(activityComponentName)
- .isEqualTo(wmState.focusedApp)
- }
-
- /**
- * Asserts that the [WindowManagerState.focusedActivity] and [WindowManagerState.focusedApp]
- * don't match [activity]
+ * Also, if [component] has a package name (i.e., is not a system component), also checks that
+ * it contains a visible [Activity] with [Activity.title] matching [component].
*
- * @param activity Component name to search
+ * @param component Component name to search
*/
- fun hasNotFocusedActivity(activity: ComponentName): WindowManagerStateSubject = apply {
- val activityComponentName = activity.toActivityName()
- check("Has focused activity")
- .that(wmState.focusedActivity)
- .isNotEqualTo(activityComponentName)
- check("Has focused app")
- .that(wmState.focusedApp)
- .isNotEqualTo(activityComponentName)
+ fun isNonAppWindowVisible(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ checkWindowVisibility("isVisible", nonAppWindows, component, isVisible = true)
}
/**
- * Asserts that the display [displayId] has a [WindowManagerState.focusedApp]
- * matching [activity]
+ * Asserts the state contains a visible window with [WindowState.title] matching [component].
*
- * @param activity Component name to search
+ * Also, if [component] has a package name (i.e., is not a system component), also checks that
+ * it contains a visible [Activity] with [Activity.title] matching [component].
+ *
+ * @param component Component name to search
*/
- @JvmOverloads
- fun hasFocusedApp(
- activity: ComponentName,
- displayId: Int = Display.DEFAULT_DISPLAY
+ fun isAppWindowVisible(
+ component: FlickerComponentName
): WindowManagerStateSubject = apply {
- val activityComponentName = activity.toActivityName()
- check("Focused app invalid")
- .that(activityComponentName)
- .isEqualTo(wmState.getDisplay(displayId)?.focusedApp)
- }
+ containsAppWindow(component)
- /**
- * Asserts that WindowManager state has a [WindowManagerState.resumedActivities]
- * matching [activity]
- *
- * @param activity Component name to search
- */
- fun hasResumedActivity(activity: ComponentName): WindowManagerStateSubject = apply {
- val activityComponentName = activity.toActivityName()
- check("Invalid resumed activity")
- .that(wmState.resumedActivities)
- .asList()
- .contains(activityComponentName)
+ val windowName = component.toWindowName()
+ // Check existence of activity
+ val activity = wmState.getActivitiesForWindow(windowName).firstOrNull()
+ // Check visibility of activity and window.
+ check("Activity=${activity?.name} must be visible.")
+ .that(activity?.isVisible ?: false).isTrue()
+ checkWindowVisibility("isVisible", appWindows, component, isVisible = true)
}
/**
- * Asserts that WindowManager state [WindowManagerState.resumedActivities] doesn't
- * match [activity]
+ * Asserts the state contains an invisible window with [WindowState.title] matching [component].
*
- * @param activity Component name to search
- */
- fun hasNotResumedActivity(activity: ComponentName): WindowManagerStateSubject = apply {
- val activityComponentName = activity.toActivityName()
- check("Has resumed activity")
- .that(wmState.resumedActivities)
- .asList()
- .doesNotContain(activityComponentName)
- }
-
- /**
- * Asserts that title of the [WindowManagerState.focusedWindow] on the state matches
- * [windowTitle]
+ * Also, if [component] has a package name (i.e., is not a system component), also checks that
+ * it contains an invisible [Activity] with [Activity.title] matching [component].
*
- * @param windowTitle window title to search
+ * @param component Component name to search
*/
- fun isFocused(windowTitle: String): WindowManagerStateSubject = apply {
- check("Invalid focused window")
- .that(windowTitle)
- .isEqualTo(wmState.focusedWindow)
- }
+ fun isAppWindowInvisible(
+ component: FlickerComponentName
+ ): WindowManagerStateSubject = apply {
+ val activityName = component.toActivityName()
- /**
- * Asserts that [WindowManagerState.focusedWindow] on the WindowManager state doesn't
- * match [windowTitle]
- *
- * @param windowTitle window title to search
- */
- fun isWindowNotFocused(windowTitle: String): WindowManagerStateSubject = apply {
- check("Has focused window")
- .that(wmState.focusedWindow)
- .isNotEqualTo(windowTitle)
+ // system components (e.g., NavBar, StatusBar, PipOverlay) don't have a package name
+ // nor an activity, ignore them
+ // activity is visible, check window
+ if (wmState.isActivityVisible(activityName)) {
+ checkWindowVisibility("isInvisible", appWindows, component, isVisible = false)
+ }
}
/**
- * Asserts that the WindowManager state contains a [WindowState] with [WindowState.title]
- * equal to [ComponentName.toWindowName] and an [Activity] with [Activity.title] equal to
- * [ComponentName.toActivityName] and both are visible
+ * Asserts the state contains an invisible window with [WindowState.title] matching [component].
*
- * @param activity Component name to search
- */
- fun isVisible(activity: ComponentName): WindowManagerStateSubject =
- hasActivityAndWindowVisibility(activity, visible = true)
-
- /**
- * Asserts that the WindowManager state contains a [WindowState] with [WindowState.title]
- * equal to [ComponentName.toWindowName] and an [Activity] with [Activity.title] equal to
- * [ComponentName.toActivityName] and both are invisible
+ * Also, if [component] has a package name (i.e., is not a system component), also checks that
+ * it contains an invisible [Activity] with [Activity.title] matching [component].
*
- * @param activity Component name to search
+ * @param component Component name to search
*/
- fun isInvisible(activity: ComponentName): WindowManagerStateSubject =
- hasActivityAndWindowVisibility(activity, visible = false)
-
- private fun hasActivityAndWindowVisibility(
- activity: ComponentName,
- visible: Boolean
+ fun isNonAppWindowInvisible(
+ component: FlickerComponentName
): WindowManagerStateSubject = apply {
- // Check existence of activity and window.
- val windowName = activity.toWindowName()
- val activityName = activity.toActivityName()
- check("Activity=$activityName must exist.")
- .that(wmState.containsActivity(activityName)).isTrue()
+ checkWindowVisibility("isInvisible", nonAppWindows, component, isVisible = false)
+ }
+
+ private fun checkWindowVisibility(
+ assertionName: String,
+ subjectList: List<WindowStateSubject>,
+ component: FlickerComponentName,
+ isVisible: Boolean
+ ) {
+ // Check existence of window.
+ contains(subjectList, component)
+
+ val windowName = component.toWindowName()
+ val foundWindows = subjectList.filter { it.name.contains(windowName) }
+ val windowsWithVisibility = foundWindows.filter { it.isVisible == isVisible }
+
+ if (windowsWithVisibility.isEmpty()) {
+ val errorTag = if (isVisible) "Is Invisible" else "Is Visible"
+ val facts = listOf<Fact>(
+ Fact.fact(ASSERTION_TAG, "$assertionName(${component.toWindowName()})"),
+ Fact.fact(errorTag, windowName)
+ )
+ foundWindows.first().fail(facts)
+ }
+ }
+
+ private fun contains(subjectList: List<WindowStateSubject>, component: FlickerComponentName) {
+ val windowName = component.toWindowName()
check("Window=$windowName must exist.")
.that(wmState.containsWindow(windowName)).isTrue()
-
- // Check visibility of activity and window.
- check("Activity=$activityName must ${if (visible) "" else " NOT"} be visible.")
- .that(visible).isEqualTo(wmState.isActivityVisible(activityName))
- check("Window=$windowName must ${if (visible) "" else " NOT"} have shown surface.")
- .that(visible).isEqualTo(wmState.isWindowSurfaceShown(windowName))
}
/**
- * Asserts that the WindowManager state home activity visibility is equal to [isVisible]
- *
- * @param isVisible if the home activity should be visible of not
+ * Asserts the state home activity is visible
*/
- @JvmOverloads
- fun isHomeActivityVisible(isVisible: Boolean = true): WindowManagerStateSubject = apply {
- if (isVisible) {
- check("Home activity doesn't exist")
- .that(wmState.homeActivity)
- .isNotNull()
-
- check("Home activity is not visible")
- .that(wmState.homeActivity?.isVisible)
- .isTrue()
- } else {
- check("Home activity is visible")
- .that(wmState.homeActivity?.isVisible ?: false)
- .isFalse()
- }
+ fun isHomeActivityVisible(): WindowManagerStateSubject = apply {
+ val homeIsVisible = wmState.homeActivity?.isVisible ?: false
+ check("Home activity doesn't exist").that(wmState.homeActivity).isNotNull()
+ check("Home activity is not visible").that(homeIsVisible).isTrue()
}
/**
- * Asserts that the IME surface is visible in the display [displayId]
+ * Asserts the state home activity is invisible
*/
- @JvmOverloads
- fun isImeWindowVisible(
- displayId: Int = Display.DEFAULT_DISPLAY
- ): WindowManagerStateSubject = apply {
- val imeWinState = wmState.inputMethodWindowState
- check("IME window must exist")
- .that(imeWinState).isNotNull()
- check("IME window must be shown")
- .that(imeWinState?.isSurfaceShown ?: false).isTrue()
- check("IME window must be on the given display")
- .that(displayId).isEqualTo(imeWinState?.displayId ?: -1)
+ fun isHomeActivityInvisible(): WindowManagerStateSubject = apply {
+ val homeIsVisible = wmState.homeActivity?.isVisible ?: false
+ check("Home activity is visible").that(homeIsVisible).isFalse()
}
/**
- * Asserts that the IME surface is invisible in the display [displayId]
+ * Asserts that [component] exists and is pinned (in PIP mode)
+ *
+ * @param component Component name to search
*/
- @JvmOverloads
- fun isImeWindowInvisible(
- displayId: Int = Display.DEFAULT_DISPLAY
- ): WindowManagerStateSubject = apply {
- val imeWinState = wmState.inputMethodWindowState
- check("IME window must not be shown")
- .that(imeWinState?.isSurfaceShown ?: false).isFalse()
- if (imeWinState?.isSurfaceShown == true) {
- check("IME window must not be on the given display")
- .that(displayId).isNotEqualTo(imeWinState.displayId)
- }
+ fun isPinned(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ contains(component)
+ val windowName = component.toWindowName()
+ val pinnedWindows = wmState.pinnedWindows.map { it.title }
+ check("Window not in PIP mode").that(pinnedWindows).contains(windowName)
}
/**
- * Asserts that an activity [activity] exists and is in PIP mode
+ * Asserts that [component] exists and is not pinned (not in PIP mode)
+ *
+ * @param component Component name to search
*/
- fun isInPipMode(
- activity: ComponentName
- ): WindowManagerStateSubject = apply {
- val windowName = activity.toWindowName()
- contains(windowName)
- val pinnedWindows = wmState.pinnedWindows
- .map { it.title }
- check("Window not in PIP mode")
- .that(pinnedWindows)
- .contains(windowName)
+ fun isNotPinned(component: FlickerComponentName): WindowManagerStateSubject = apply {
+ contains(component)
+ val windowName = component.toWindowName()
+ val pinnedWindows = wmState.pinnedWindows.map { it.title }
+ check("Window not in PIP mode").that(pinnedWindows).doesNotContain(windowName)
}
/**
- * Obtains a [WindowStateSubject] for the first occurrence of a [WindowState] with
- * [WindowState.title] containing [name].
+ * Obtains the first subject with [WindowState.title] containing [name].
*
* Always returns a subject, event when the layer doesn't exist. To verify if layer
* actually exists in the hierarchy use [WindowStateSubject.exists] or
* [WindowStateSubject.doesNotExist]
- *
- * @return WindowStateSubject that can be used to make assertions on a single [WindowState]
- * matching [name].
*/
fun windowState(name: String): WindowStateSubject {
return subjects.firstOrNull {
it.windowState?.name?.contains(name) == true
- } ?: WindowStateSubject.assertThat(name, this)
+ } ?: WindowStateSubject.assertThat(name, this, timestamp)
+ }
+
+ /**
+ * Obtains the first subject matching [predicate].
+ *
+ * Always returns a subject, event when the layer doesn't exist. To verify if layer
+ * actually exists in the hierarchy use [WindowStateSubject.exists] or
+ * [WindowStateSubject.doesNotExist]
+ *
+ * @param predicate to search for a subject
+ * @param name Name of the subject to use when not found (optional)
+ */
+ fun windowState(name: String = "", predicate: (WindowState) -> Boolean): WindowStateSubject {
+ return subjects.firstOrNull {
+ it.windowState?.run { predicate(this) } ?: false
+ } ?: WindowStateSubject.assertThat(name, this, timestamp)
}
override fun toString(): String {
@@ -675,28 +553,30 @@ class WindowManagerStateSubject private constructor(
/**
* Boiler-plate Subject.Factory for WindowManagerStateSubject
*
- * @param trace containing the entry
+ * @param parent containing the entry
*/
private fun getFactory(
- trace: WindowManagerTraceSubject? = null
+ trace: WindowManagerTraceSubject?,
+ parent: FlickerSubject?
): Factory<Subject, WindowManagerState> =
- Factory { fm, subject -> WindowManagerStateSubject(fm, subject, trace) }
+ Factory { fm, subject -> WindowManagerStateSubject(fm, subject, trace, parent) }
/**
* User-defined entry point
*
* @param entry to assert
- * @param trace containing the entry
+ * @param parent containing the entry
*/
@JvmStatic
@JvmOverloads
fun assertThat(
entry: WindowManagerState,
- trace: WindowManagerTraceSubject? = null
+ trace: WindowManagerTraceSubject? = null,
+ parent: FlickerSubject? = null
): WindowManagerStateSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(trace))
+ .about(getFactory(trace, parent))
.that(entry) as WindowManagerStateSubject
strategy.init(subject)
return subject
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerTraceSubject.kt
index c790f970a..ed77b20de 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerTraceSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowManagerTraceSubject.kt
@@ -20,11 +20,12 @@ import com.android.server.wm.flicker.assertions.Assertion
import com.android.server.wm.flicker.assertions.FlickerSubject
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.traces.FlickerTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.Region
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.FailureStrategy
import com.google.common.truth.StandardSubjectBuilder
@@ -55,46 +56,32 @@ import com.google.common.truth.Subject
*/
class WindowManagerTraceSubject private constructor(
fm: FailureMetadata,
- val trace: WindowManagerTrace
+ val trace: WindowManagerTrace,
+ override val parent: WindowManagerTraceSubject?
) : FlickerTraceSubject<WindowManagerStateSubject>(fm, trace) {
- override val defaultFacts: String = buildString {
- if (trace.hasSource()) {
- append("Path: ${trace.source}")
- append("\n")
- }
- append("Trace: $trace")
- }
+ override val selfFacts
+ get() = super.selfFacts.toMutableList()
+ .also {
+ if (trace.hasSource()) {
+ it.add(Fact.fact("Trace file", trace.source))
+ }
+ }
override val subjects by lazy {
- trace.entries.map { WindowManagerStateSubject.assertThat(it, this) }
+ trace.entries.map { WindowManagerStateSubject.assertThat(it, this, this) }
}
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return WindowManagerTraceSubject(fm, trace)
+ return WindowManagerTraceSubject(fm, trace, parent)
}
- /**
- * Signal that the last assertion set is complete. The next assertion added will start a new
- * set of assertions.
- *
- * E.g.: checkA().then().checkB()
- *
- * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
- * after checkA passes.
- */
- fun then(): WindowManagerTraceSubject =
- apply { startAssertionBlock() }
+ /** {@inheritDoc} */
+ override fun then(): WindowManagerTraceSubject = apply { super.then() }
- /**
- * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
- * end of the trace without passing any assertion, return a failure with the name/reason from
- * the first assertion
- *
- * @return
- */
- fun skipUntilFirstAssertion(): WindowManagerTraceSubject =
- apply { assertionsChecker.skipUntilFirstAssertion() }
+ /** {@inheritDoc} */
+ override fun skipUntilFirstAssertion(): WindowManagerTraceSubject =
+ apply { super.skipUntilFirstAssertion() }
fun isEmpty(): WindowManagerTraceSubject = apply {
check("Trace is empty").that(trace).isEmpty()
@@ -105,353 +92,439 @@ class WindowManagerTraceSubject private constructor(
}
/**
- * Checks if the non-app window with title containing [partialWindowTitle] exists above the app
+ * @return List of [WindowStateSubject]s matching [partialWindowTitle] in the order they
+ * appear on the trace
+ */
+ fun windowStates(partialWindowTitle: String): List<WindowStateSubject> {
+ return subjects
+ .map { it.windowState { windows -> windows.title.contains(partialWindowTitle) } }
+ .filter { it.isNotEmpty }
+ }
+
+ /**
+ * @return List of [WindowStateSubject]s matching [predicate] in the order they
+ * appear on the trace
+ */
+ fun windowStates(predicate: (WindowState) -> Boolean): List<WindowStateSubject> {
+ return subjects
+ .map { it.windowState { window -> predicate(window) } }
+ .filter { it.isNotEmpty }
+ }
+
+ /** {@inheritDoc} */
+ fun notContains(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("notContains(${component.toWindowName()})", isOptional) {
+ it.notContains(component)
+ }
+ }
+
+ /**
+ * Checks if the non-app window with title containing [component] exists above the app
* windows and is visible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun showsAboveAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("showsAboveAppWindow($partialWindowTitle)") {
- it.isAboveAppWindow(*partialWindowTitle)
+ @JvmOverloads
+ fun isAboveAppWindowVisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isAboveAppWindowVisible(${component.toWindowName()})", isOptional) {
+ it.containsAboveAppWindow(component)
+ .isNonAppWindowVisible(component)
}
}
/**
- * Checks if the non-app window with title containing [partialWindowTitle] exists above the app
+ * Checks if the non-app window with title containing [component] exists above the app
* windows and is invisible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun hidesAboveAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("hidesAboveAppWindow($partialWindowTitle)") {
- it.isAboveAppWindow(*partialWindowTitle, isVisible = false)
+ @JvmOverloads
+ fun isAboveAppWindowInvisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isAboveAppWindowInvisible(${component.toWindowName()})", isOptional) {
+ it.containsAboveAppWindow(component)
+ .isNonAppWindowInvisible(component)
}
}
/**
- * Checks if the non-app window with title containing [partialWindowTitle] exists below the app
+ * Checks if the non-app window with title containing [component] exists below the app
* windows and is visible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun showsBelowAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("showsBelowAppWindow($partialWindowTitle)") {
- it.isBelowAppWindow(*partialWindowTitle)
+ @JvmOverloads
+ fun isBelowAppWindowVisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isBelowAppWindowVisible(${component.toWindowName()})", isOptional) {
+ it.containsBelowAppWindow(component)
+ .isNonAppWindowVisible(component)
}
}
/**
- * Checks if the non-app window with title containing [partialWindowTitle] exists below the app
+ * Checks if the non-app window with title containing [component] exists below the app
* windows and is invisible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun hidesBelowAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("hidesBelowAppWindow($partialWindowTitle)") {
- it.isBelowAppWindow(*partialWindowTitle, isVisible = false)
+ @JvmOverloads
+ fun isBelowAppWindowInvisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isBelowAppWindowInvisible(${component.toWindowName()})", isOptional) {
+ it.containsBelowAppWindow(component)
+ .isNonAppWindowInvisible(component)
}
}
/**
- * Checks if non-app window with title containing the [partialWindowTitle] exists above or
+ * Checks if non-app window with title containing the [component] exists above or
* below the app windows and is visible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun showsNonAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("showsNonAppWindow($partialWindowTitle)") {
- it.containsNonAppWindow(*partialWindowTitle)
+ @JvmOverloads
+ fun isNonAppWindowVisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isNonAppWindowVisible(${component.toWindowName()})", isOptional) {
+ it.isNonAppWindowVisible(component)
}
}
/**
- * Checks if non-app window with title containing the [partialWindowTitle] exists above or
+ * Checks if non-app window with title containing the [component] exists above or
* below the app windows and is invisible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun hidesNonAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("hidesNonAppWindow($partialWindowTitle)") {
- it.containsNonAppWindow(*partialWindowTitle, isVisible = false)
+ @JvmOverloads
+ fun isNonAppWindowInvisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isNonAppWindowInvisible(${component.toWindowName()})", isOptional) {
+ it.isNonAppWindowInvisible(component)
}
}
/**
- * Checks if an app window with title containing the [partialWindowTitles] is on top
+ * Checks if app window with title containing the [component] is on top
*
- * @param partialWindowTitles window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun showsAppWindowOnTop(vararg partialWindowTitles: String): WindowManagerTraceSubject = apply {
- val assertionName = "showsAppWindowOnTop(${partialWindowTitles.joinToString(",")})"
- addAssertion(assertionName) {
- check("No window titles to search")
- .that(partialWindowTitles)
- .isNotEmpty()
- it.showsAppWindowOnTop(*partialWindowTitles)
+ @JvmOverloads
+ fun isAppWindowOnTop(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isAppWindowOnTop(${component.toWindowName()})", isOptional) {
+ it.isAppWindowOnTop(component)
}
}
/**
- * Checks if app window with title containing the [partialWindowTitle] is not on top
+ * Checks if app window with title containing the [component] is not on top
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun appWindowNotOnTop(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("hidesAppWindowOnTop($partialWindowTitle)") {
- it.containsAppWindow(*partialWindowTitle, isVisible = false)
+ @JvmOverloads
+ fun isAppWindowNotOnTop(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("appWindowNotOnTop(${component.toWindowName()})", isOptional) {
+ it.isAppWindowNotOnTop(component)
}
}
/**
- * Checks if app window with title containing the [partialWindowTitle] is visible
+ * Checks if app window with title containing the [component] is visible
*
- * @param partialWindowTitle window title to search
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun showsAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("showsAppWindow($partialWindowTitle)") {
- it.containsAppWindow(*partialWindowTitle, isVisible = true)
+ @JvmOverloads
+ fun isAppWindowVisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isAppWindowVisible(${component.toWindowName()})", isOptional) {
+ it.isAppWindowVisible(component)
}
}
/**
- * Checks if app window with title containing the [partialWindowTitle] is invisible
+ * Checks if app window with title containing the [component] is invisible
*
- * @param partialWindowTitle window title to search
+ * Note: This assertion have issues with the launcher window, because it contains 2 windows
+ * with the same name and only 1 is visible at a time. Prefer [isAppWindowOnTop] for launcher
+ * instead
+ *
+ * @param component Component to search
+ * @param isOptional If this assertion is optional or must pass
*/
- fun hidesAppWindow(vararg partialWindowTitle: String): WindowManagerTraceSubject = apply {
- addAssertion("hidesAppWindow($partialWindowTitle)") {
- it.containsAppWindow(*partialWindowTitle, isVisible = false)
+ @JvmOverloads
+ fun isAppWindowInvisible(
+ component: FlickerComponentName,
+ isOptional: Boolean = false
+ ): WindowManagerTraceSubject = apply {
+ addAssertion("isAppWindowInvisible(${component.toWindowName()})", isOptional) {
+ it.isAppWindowInvisible(component)
}
}
/**
- * Checks if no app windows containing the [partialWindowTitles] overlap with each other.
+ * Checks if no app windows containing the [component] overlap with each other.
*
- * @param partialWindowTitles partial titles of windows to check
+ * @param component Component to search
*/
- fun noWindowsOverlap(vararg partialWindowTitles: String): WindowManagerTraceSubject = apply {
- val repr = partialWindowTitles.joinToString(", ")
- require(partialWindowTitles.size > 1) {
- "Must give more than one window to check! (Given $repr)"
- }
+ fun noWindowsOverlap(
+ vararg component: FlickerComponentName
+ ): WindowManagerTraceSubject = apply {
+ val repr = component.joinToString(", ") { it.toWindowName() }
+ verify("Must give more than one window to check! (Given $repr)")
+ .that(component)
+ .hasLength(1)
addAssertion("noWindowsOverlap($repr)") {
- it.noWindowsOverlap(*partialWindowTitles)
+ it.doNotOverlap(*component)
}
}
/**
- * Checks if the window named [aboveWindowTitle] is above the one named [belowWindowTitle] in
+ * Checks if the window named [aboveWindow] is above the one named [belowWindow] in
* z-order.
*
- * @param aboveWindowTitle partial name of the expected top window
- * @param belowWindowTitle partial name of the expected bottom window
+ * @param aboveWindow Expected top window
+ * @param belowWindow Expected bottom window
*/
fun isAboveWindow(
- aboveWindowTitle: String,
- belowWindowTitle: String
+ aboveWindow: FlickerComponentName,
+ belowWindow: FlickerComponentName
): WindowManagerTraceSubject = apply {
+ val aboveWindowTitle = aboveWindow.toWindowName()
+ val belowWindowTitle = belowWindow.toWindowName()
require(aboveWindowTitle != belowWindowTitle)
addAssertion("$aboveWindowTitle is above $belowWindowTitle") {
- it.isAboveWindow(aboveWindowTitle, belowWindowTitle)
+ it.isAboveWindow(aboveWindow, belowWindow)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at least [testRegion], that is, if its area of the
- * window's bounds cover each point in the region.
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at least
+ * [testRegion], that is, if its area of the window's bounds cover each point in the region.
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRegion Expected visible area of the window
*/
fun coversAtLeast(
testRegion: Region,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtLeastRegion($partialWindowTitle, $testRegion)") {
- it.frameRegion(partialWindowTitle).coversAtLeast(testRegion)
+ addAssertion("coversAtLeastRegion(${component?.toWindowName()}, $testRegion)") {
+ it.frameRegion(component).coversAtLeast(testRegion)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at least [testRegion], that is, if its area of the
- * window's bounds cover each point in the region.
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at least
+ * [testRegion], that is, if its area of the window's bounds cover each point in the region.
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRegion Expected visible area of the window
*/
fun coversAtLeast(
testRegion: android.graphics.Region,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtLeastRegion($partialWindowTitle, $testRegion)") {
- it.frameRegion(partialWindowTitle).coversAtLeast(testRegion)
+ addAssertion("coversAtLeastRegion(${component?.toWindowName()}, $testRegion)") {
+ it.frameRegion(component).coversAtLeast(testRegion)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at least [testRect], that is, if its area of the
- * window's bounds cover each point in the region.
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at least
+ * [testRect], that is, if its area of the window's bounds cover each point in the region.
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRect Expected visible area of the window
*/
fun coversAtLeast(
testRect: android.graphics.Rect,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtLeastRegion($partialWindowTitle, $testRect)") {
- it.frameRegion(partialWindowTitle).coversAtLeast(testRect)
+ addAssertion("coversAtLeastRegion(${component?.toWindowName()}, $testRect)") {
+ it.frameRegion(component).coversAtLeast(testRect)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at least [testRect], that is, if its area of the
- * window's bounds cover each point in the region.
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at least
+ * [testRect], that is, if its area of the window's bounds cover each point in the region.
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRect Expected visible area of the window
*/
fun coversAtLeast(
testRect: Rect,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtLeastRegion($partialWindowTitle, $testRect)") {
- it.frameRegion(partialWindowTitle).coversAtLeast(testRect)
+ addAssertion("coversAtLeastRegion(${component?.toWindowName()}, $testRect)") {
+ it.frameRegion(component).coversAtLeast(testRect)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at most [testRegion], that is, if the area of the
- * window state bounds don't cover any point outside of [testRegion].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at most
+ * [testRegion], that is, if the area of the window state bounds don't cover any point outside
+ * of [testRegion].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRegion Expected visible area of the window
*/
fun coversAtMost(
testRegion: Region,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtMostRegion($partialWindowTitle, $testRegion)") {
- it.frameRegion(partialWindowTitle).coversAtMost(testRegion)
+ addAssertion("coversAtMostRegion(${component?.toWindowName()}, $testRegion)") {
+ it.frameRegion(component).coversAtMost(testRegion)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at most [testRegion], that is, if the area of the
- * window state bounds don't cover any point outside of [testRegion].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at most
+ * [testRegion], that is, if the area of the window state bounds don't cover any point outside
+ * of [testRegion].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRegion Expected visible area of the window
*/
fun coversAtMost(
testRegion: android.graphics.Region,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtMostRegion($partialWindowTitle, $testRegion)") {
- it.frameRegion(partialWindowTitle).coversAtMost(testRegion)
+ addAssertion("coversAtMostRegion(${component?.toWindowName()}, $testRegion)") {
+ it.frameRegion(component).coversAtMost(testRegion)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at most [testRect], that is, if the area of the
- * window state bounds don't cover any point outside of [testRect].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at most
+ * [testRect], that is, if the area of the window state bounds don't cover any point outside
+ * of [testRect].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRect Expected visible area of the window
*/
fun coversAtMost(
testRect: Rect,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtMostRegion($partialWindowTitle, $testRect)") {
- it.frameRegion(partialWindowTitle).coversAtMost(testRect)
+ addAssertion("coversAtMostRegion(${component?.toWindowName()}, $testRect)") {
+ it.frameRegion(component).coversAtMost(testRect)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers at most [testRect], that is, if the area of the
- * window state bounds don't cover any point outside of [testRect].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers at most
+ * [testRect], that is, if the area of the window state bounds don't cover any point outside
+ * of [testRect].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRect Expected visible area of the window
*/
fun coversAtMost(
testRect: android.graphics.Rect,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversAtMostRegion($partialWindowTitle, $testRect)") {
- it.frameRegion(partialWindowTitle).coversAtMost(testRect)
+ addAssertion("coversAtMostRegion(${component?.toWindowName()}, $testRect)") {
+ it.frameRegion(component).coversAtMost(testRect)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers exactly [testRegion].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers exactly
+ * [testRegion].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRegion Expected visible area of the window
*/
fun coversExactly(
testRegion: android.graphics.Region,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversExactly($partialWindowTitle, $testRegion)") {
- it.frameRegion(partialWindowTitle).coversExactly(testRegion)
+ addAssertion("coversExactly(${component?.toWindowName()}, $testRegion)") {
+ it.frameRegion(component).coversExactly(testRegion)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers exactly [testRect].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers exactly
+ * [testRegion].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRect Expected visible area of the window
*/
fun coversExactly(
testRect: Rect,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversExactly($partialWindowTitle, $testRect)") {
- it.frameRegion(partialWindowTitle).coversExactly(testRect)
+ addAssertion("coversExactly(${component?.toWindowName()}, $testRect)") {
+ it.frameRegion(component).coversExactly(testRect)
}
}
/**
- * Asserts that the visible area covered by the first [WindowState] with [WindowState.title]
- * containing [partialWindowTitle] covers exactly [testRect].
+ * Asserts the visible area covered by the [WindowState]s matching [component] covers exactly
+ * [testRect].
*
- * @param partialWindowTitle Name of the layer to search
+ * @param component Component to search
* @param testRect Expected visible area of the window
*/
fun coversExactly(
testRect: android.graphics.Rect,
- partialWindowTitle: String
+ component: FlickerComponentName?
): WindowManagerTraceSubject = apply {
- addAssertion("coversExactly($partialWindowTitle, $testRect)") {
- it.frameRegion(partialWindowTitle).coversExactly(testRect)
+ addAssertion("coversExactly(${component?.toWindowName()}, $testRect)") {
+ it.frameRegion(component).coversExactly(testRect)
}
}
/**
* Checks that all visible layers are shown for more than one consecutive entry
*/
+ @JvmOverloads
fun visibleWindowsShownMoreThanOneConsecutiveEntry(
- ignoreWindows: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ ignoreWindows: List<FlickerComponentName> = listOf(
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
): WindowManagerTraceSubject = apply {
visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
subject.wmState.windowStates
.filter { it.isVisible }
.filter {
- ignoreWindows.none { windowName -> windowName in it.title }
+ ignoreWindows.none { windowName -> windowName.toWindowName() in it.title }
}
.map { it.name }
.toSet()
@@ -463,8 +536,9 @@ class WindowManagerTraceSubject private constructor(
*/
operator fun invoke(
name: String,
+ isOptional: Boolean = false,
assertion: Assertion<WindowManagerStateSubject>
- ): WindowManagerTraceSubject = apply { addAssertion(name, assertion) }
+ ): WindowManagerTraceSubject = apply { addAssertion(name, isOptional, assertion) }
/**
* Run the assertions for all trace entries within the specified time range
@@ -486,8 +560,10 @@ class WindowManagerTraceSubject private constructor(
/**
* Boiler-plate Subject.Factory for WmTraceSubject
*/
- private val FACTORY: Factory<Subject, WindowManagerTrace> =
- Factory { fm, subject -> WindowManagerTraceSubject(fm, subject) }
+ private fun getFactory(
+ parent: WindowManagerTraceSubject?
+ ): Factory<Subject, WindowManagerTrace> =
+ Factory { fm, subject -> WindowManagerTraceSubject(fm, subject, parent) }
/**
* Creates a [WindowManagerTraceSubject] representing a WindowManager trace,
@@ -496,10 +572,14 @@ class WindowManagerTraceSubject private constructor(
* @param trace WindowManager trace
*/
@JvmStatic
- fun assertThat(trace: WindowManagerTrace): WindowManagerTraceSubject {
+ @JvmOverloads
+ fun assertThat(
+ trace: WindowManagerTrace,
+ parent: WindowManagerTraceSubject? = null
+ ): WindowManagerTraceSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(FACTORY)
+ .about(getFactory(parent))
.that(trace) as WindowManagerTraceSubject
strategy.init(subject)
return subject
@@ -509,6 +589,8 @@ class WindowManagerTraceSubject private constructor(
* Static method for getting the subject factory (for use with assertAbout())
*/
@JvmStatic
- fun entries(): Factory<Subject, WindowManagerTrace> = FACTORY
+ fun entries(
+ parent: WindowManagerTraceSubject?
+ ): Factory<Subject, WindowManagerTrace> = getFactory(parent)
}
}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowStateSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowStateSubject.kt
index 3666d0328..41a31192c 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowStateSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/traces/windowmanager/WindowStateSubject.kt
@@ -21,6 +21,7 @@ import com.android.server.wm.flicker.assertions.FlickerSubject
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.traces.RegionSubject
import com.android.server.wm.traces.common.windowmanager.windows.WindowState
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.FailureStrategy
import com.google.common.truth.StandardSubjectBuilder
@@ -45,8 +46,9 @@ import com.google.common.truth.StandardSubjectBuilder
*/
class WindowStateSubject private constructor(
fm: FailureMetadata,
+ override val parent: WindowManagerStateSubject?,
+ override val timestamp: Long,
val windowState: WindowState?,
- private val entry: WindowManagerStateSubject?,
private val windowTitle: String? = null
) : FlickerSubject(fm, windowState) {
val isEmpty: Boolean get() = windowState == null
@@ -54,10 +56,10 @@ class WindowStateSubject private constructor(
val isVisible: Boolean get() = windowState?.isVisible == true
val isInvisible: Boolean get() = windowState?.isVisible == false
val name: String get() = windowState?.name ?: windowTitle ?: ""
- val frame: RegionSubject get() = RegionSubject.assertThat(windowState?.frame, listOf(this))
+ val frame: RegionSubject get() = RegionSubject.assertThat(windowState?.frame, this)
- override val defaultFacts: String =
- "${entry?.defaultFacts ?: ""}\nWindowTitle: ${windowState?.title}"
+ override val selfFacts = listOf(
+ Fact.fact("Window title", "${windowState?.title ?: windowTitle}"))
/**
* If the [windowState] exists, executes a custom [assertion] on the current subject
@@ -69,7 +71,7 @@ class WindowStateSubject private constructor(
/** {@inheritDoc} */
override fun clone(): FlickerSubject {
- return WindowStateSubject(fm, windowState, entry, windowTitle)
+ return WindowStateSubject(fm, parent, timestamp, windowState, windowTitle)
}
/**
@@ -95,10 +97,9 @@ class WindowStateSubject private constructor(
* Boiler-plate Subject.Factory for LayerSubject
*/
@JvmStatic
- @JvmOverloads
- fun getFactory(entry: WindowManagerStateSubject? = null) =
+ fun getFactory(parent: WindowManagerStateSubject?, timestamp: Long, name: String?) =
Factory { fm: FailureMetadata, subject: WindowState? ->
- WindowStateSubject(fm, subject, entry)
+ WindowStateSubject(fm, parent, timestamp, subject, name)
}
/**
@@ -107,13 +108,14 @@ class WindowStateSubject private constructor(
@JvmStatic
@JvmOverloads
fun assertThat(
- layer: WindowState?,
- entry: WindowManagerStateSubject? = null
+ state: WindowState?,
+ parent: WindowManagerStateSubject? = null,
+ timestamp: Long
): WindowStateSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(entry))
- .that(layer) as WindowStateSubject
+ .about(getFactory(parent, timestamp, name = null))
+ .that(state) as WindowStateSubject
strategy.init(subject)
return subject
}
@@ -124,23 +126,15 @@ class WindowStateSubject private constructor(
@JvmStatic
internal fun assertThat(
name: String,
- entry: WindowManagerStateSubject?
+ parent: WindowManagerStateSubject?,
+ timestamp: Long
): WindowStateSubject {
val strategy = FlickerFailureStrategy()
val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
- .about(getFactory(entry, name))
+ .about(getFactory(parent, timestamp, name))
.that(null) as WindowStateSubject
strategy.init(subject)
return subject
}
-
- /**
- * Boiler-plate Subject.Factory for LayerSubject
- */
- @JvmStatic
- internal fun getFactory(entry: WindowManagerStateSubject?, name: String) =
- Factory { fm: FailureMetadata, subject: WindowState? ->
- WindowStateSubject(fm, subject, entry, name)
- }
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/proto/errors.proto b/libraries/flicker/src/com/android/server/wm/proto/errors.proto
new file mode 100644
index 000000000..94427bb81
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/proto/errors.proto
@@ -0,0 +1,35 @@
+syntax = "proto2";
+
+package com.android.server.wm.flicker;
+
+// Each message has its own class file created.
+option java_multiple_files = true;
+
+message FlickerErrorProto {
+ required string stacktrace = 1;
+ required string message = 2;
+ optional int32 layerId = 3;
+ optional string windowToken = 4;
+ optional int32 taskId = 5;
+ optional string assertionName = 6;
+}
+
+message FlickerErrorStateProto {
+ required int64 timestamp = 1;
+ repeated FlickerErrorProto errors = 2;
+}
+
+message FlickerErrorTraceProto {
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 |
+ MagicNumber.MAGIC_NUMBER_L (this is needed because enums have to be 32 bits
+ and there's no nice way to put 64bit constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x54525245; /* ERRT (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ /* Must be the first field, set to value in MagicNumber */
+ optional fixed64 magic_number = 1;
+ repeated FlickerErrorStateProto states = 2;
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/proto/tags.proto b/libraries/flicker/src/com/android/server/wm/proto/tags.proto
new file mode 100644
index 000000000..dd352b50b
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/proto/tags.proto
@@ -0,0 +1,45 @@
+syntax = "proto2";
+
+package com.android.server.wm.flicker;
+
+// Each message has its own class file created.
+option java_multiple_files = true;
+
+message FlickerTagProto {
+ enum Transition {
+ ROTATION = 0;
+ APP_LAUNCH = 1;
+ APP_CLOSE = 2;
+ IME_APPEAR = 3;
+ IME_DISAPPEAR = 4;
+ PIP_ENTER = 5;
+ PIP_RESIZE = 6;
+ PIP_EXIT = 7;
+ };
+ required bool isStartTag = 1;
+ required Transition transition = 2;
+ required int32 id = 3;
+ optional int32 layerId = 4;
+ optional string windowToken = 5;
+ optional int32 taskId = 6;
+}
+
+message FlickerTagStateProto {
+ required int64 timestamp = 1;
+ repeated FlickerTagProto tags = 2;
+}
+
+message FlickerTagTraceProto {
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 |
+ MagicNumber.MAGIC_NUMBER_L (this is needed because enums have to be 32 bits
+ and there's no nice way to put 64bit constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x54474154; /* TAGT (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ /* Must be the first field, set to value in MagicNumber */
+ optional fixed64 magic_number = 1;
+ repeated FlickerTagStateProto states = 2;
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Buffer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Buffer.kt
index 25e221e76..46a6227c8 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Buffer.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Buffer.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.traces.common
-class Buffer(width: Int, height: Int, val stride: Int, val format: Int) : Bounds(width, height) {
+class Buffer(width: Int, height: Int, val stride: Int, val format: Int) : Size(width, height) {
override fun prettyPrint(): String = prettyPrint(this)
override fun equals(other: Any?): Boolean =
@@ -34,6 +34,8 @@ class Buffer(width: Int, height: Int, val stride: Int, val format: Int) : Bounds
return result
}
+ override fun toString(): String = prettyPrint()
+
companion object {
val EMPTY: Buffer = Buffer(0, 0, 0, 0)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt
index a4334feca..785977e70 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt
@@ -27,6 +27,26 @@ data class Color(val r: Float, val g: Float, val b: Float, val a: Float) {
override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Color) return false
+
+ if (r != other.r) return false
+ if (g != other.g) return false
+ if (b != other.b) return false
+ if (a != other.a) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = r.hashCode()
+ result = 31 * result + g.hashCode()
+ result = 31 * result + b.hashCode()
+ result = 31 * result + a.hashCode()
+ return result
+ }
+
companion object {
val EMPTY = Color(r = -1f, g = -1f, b = -1f, a = 0f)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt
new file mode 100644
index 000000000..3645304c0
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+/**
+ * The utility class to wait a condition with customized options.
+ * The default retry policy is 5 times with interval 1 second.
+ *
+ * @param <T> The type of the object to validate.
+ *
+ * <p>Sample:</p>
+ * <pre>
+ * // Simple case.
+ * if (Condition.waitFor("true value", () -> true)) {
+ * println("Success");
+ * }
+ * // Wait for customized result with customized validation.
+ * String result = Condition.waitForResult(new Condition<String>("string comparison")
+ * .setResultSupplier(() -> "Result string")
+ * .setResultValidator(str -> str.equals("Expected string"))
+ * .setRetryIntervalMs(500)
+ * .setRetryLimit(3)
+ * .setOnFailure(str -> println("Failed on " + str)));
+ * </pre>
+
+ * @param message The message to show what is waiting for.
+ * @param condition If it returns true, that means the condition is satisfied.
+ */
+open class Condition<T>(
+ protected open val message: String = "",
+ protected open val condition: (T) -> Boolean
+) {
+ /**
+ * @return if [value] satisfies the condition
+ */
+ fun isSatisfied(value: T): Boolean {
+ return condition.invoke(value)
+ }
+
+ /**
+ * @return the negation of the current assertion
+ */
+ fun negate(): Condition<T> = Condition(
+ message = "!$message") {
+ !this.condition.invoke(it)
+ }
+
+ /**
+ * @return a formatted message for the passing or failing condition on a state
+ */
+ open fun getMessage(value: T): String = "$message(passed=${isSatisfied(value)})"
+
+ override fun toString(): String = this.message
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt
new file mode 100644
index 000000000..acc6a5fbd
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+/**
+ * The utility class to validate a set of conditions
+ *
+ * This class is used to easily integrate multiple conditions into a single
+ * verification, for example, during [WaitCondition], while keeping the individual
+ * conditions separate for better reuse
+ *
+ * @param conditions conditions to be checked
+ */
+class ConditionList<T>(
+ val conditions: List<Condition<T>>
+) : Condition<T>("", { false }) {
+ override val message: String
+ get() = conditions.joinToString(" and ") { it.toString() }
+
+ override val condition: (T) -> Boolean
+ get() = {
+ conditions.all { condition -> condition.isSatisfied(it) }
+ }
+
+ override fun getMessage(value: T): String = conditions
+ .joinToString(" and ") { it.getMessage(value) }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt b/libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt
new file mode 100644
index 000000000..c96cd6cae
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * Represents a state dump containing the [WindowManagerState] and the [LayerTraceEntry] both
+ * parsed and in raw (byte) data.
+ */
+class DeviceStateDump<WMType : WindowManagerState?, LayerType : LayerTraceEntry?>(
+ /**
+ * Parsed [WindowManagerState]
+ */
+ val wmState: WMType,
+ /**
+ * Parsed [LayerTraceEntry]
+ */
+ val layerState: LayerType
+) \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt b/libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt
new file mode 100644
index 000000000..f856a449e
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * Represents a state dump containing the [WindowManagerTrace] and the [LayersTrace] both parsed
+ * and in raw (byte) data.
+ */
+class DeviceTraceDump(
+ /**
+ * Parsed [WindowManagerTrace]
+ */
+ val wmTrace: WindowManagerTrace?,
+ /**
+ * Parsed [LayersTrace]
+ */
+ val layersTrace: LayersTrace?
+) \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Extensions.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Extensions.kt
index 1b24bae36..efffd11af 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Extensions.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Extensions.kt
@@ -22,7 +22,7 @@ private const val MINUTE_AS_NANOSECONDS: Long = 60000000000
private const val HOUR_AS_NANOSECONDS: Long = 3600000000000
private const val DAY_AS_NANOSECONDS: Long = 86400000000000
-internal fun prettyTimestamp(timestampNs: Long): String {
+fun prettyTimestamp(timestampNs: Long): String {
// Necessary for compatibility with JS Number
var remainingNs = "$timestampNs".toLong()
val prettyTimestamp = StringBuilder()
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/FlickerComponentName.kt b/libraries/flicker/src/com/android/server/wm/traces/common/FlickerComponentName.kt
new file mode 100644
index 000000000..c39a3bf7b
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/FlickerComponentName.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+/**
+ * Create a new component identifier.
+ *
+ * This is a version of Android's ComponentName class for flicker. This is necessary because
+ * flicker codebase it also compiled into KotlinJS for use into Winscope
+ *
+ * @param packageName The name of the package that the component exists in. Can
+ * not be null.
+ * @param className The name of the class inside of <var>pkg</var> that
+ * implements the component. Can not be null.
+ */
+data class FlickerComponentName(
+ val packageName: String,
+ val className: String
+) {
+ /**
+ * Obtains the activity name from the component name.
+ *
+ * See [ComponentName.toWindowName] for additional information
+ */
+ fun toActivityName(): String {
+ return when {
+ packageName.isNotEmpty() && className.isNotEmpty() -> {
+ val sb = StringBuilder(packageName.length + className.length)
+ appendShortString(sb, packageName, className)
+ return sb.toString()
+ }
+ packageName.isNotEmpty() -> packageName
+ className.isNotEmpty() -> className
+ else -> error("Component name should have an activity of class name")
+ }
+ }
+
+ /**
+ * Obtains the window name from the component name.
+ *
+ * [ComponentName] builds the string representation as PKG/CLASS, however this doesn't
+ * work for system components such as IME, NavBar and StatusBar, Toast.
+ *
+ * If the component doesn't have a package name, assume it's a system component and return only
+ * the class name
+ */
+ fun toWindowName(): String {
+ return when {
+ packageName.isNotEmpty() && className.isNotEmpty() -> "$packageName/$className"
+ packageName.isNotEmpty() -> packageName
+ className.isNotEmpty() -> className
+ else -> error("Component name should have an activity of class name")
+ }
+ }
+
+ /**
+ * Obtains the layer name from the component name.
+ *
+ * See [toWindowName] for additional information
+ */
+ fun toLayerName(): String {
+ var result = this.toWindowName()
+ if (result.contains("/") && !result.contains("#")) {
+ result = "$result#"
+ }
+
+ return result
+ }
+
+ private fun appendShortString(sb: StringBuilder, packageName: String, className: String) {
+ sb.append(packageName).append('/')
+ appendShortClassName(sb, packageName, className)
+ }
+
+ private fun appendShortClassName(sb: StringBuilder, packageName: String, className: String) {
+ if (className.startsWith(packageName)) {
+ val packageNameLength = packageName.length
+ val classNameLength = className.length
+ if (classNameLength > packageNameLength && className[packageNameLength] == '.') {
+ sb.append(className, packageNameLength, classNameLength)
+ return
+ }
+ }
+ sb.append(className)
+ }
+
+ companion object {
+ val NAV_BAR = FlickerComponentName("", "NavigationBar0")
+ val STATUS_BAR = FlickerComponentName("", "StatusBar")
+ val ROTATION = FlickerComponentName("", "RotationLayer")
+ val BACK_SURFACE = FlickerComponentName("", "BackColorSurface")
+ val IME = FlickerComponentName("", "InputMethod")
+ val SPLASH_SCREEN = FlickerComponentName("", "Splash Screen")
+ val SNAPSHOT = FlickerComponentName("", "SnapshotStartingWindow")
+ val WALLPAPER_BBQ_WRAPPER =
+ FlickerComponentName("", "Wallpaper BBQ wrapper")
+
+ fun unflattenFromString(str: String): FlickerComponentName {
+ val sep = str.indexOf('/')
+ if (sep < 0 || sep + 1 >= str.length) {
+ error("Missing package/class separator")
+ }
+ val pkg = str.substring(0, sep)
+ var cls = str.substring(sep + 1)
+ if (cls.isNotEmpty() && cls[0] == '.') {
+ cls = pkg + cls
+ }
+ return FlickerComponentName(pkg, cls)
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt
index 6dc532db8..75af785dd 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt
@@ -17,9 +17,8 @@
package com.android.server.wm.traces.common
interface ITrace<Entry : ITraceEntry> {
- val entries: List<Entry>
+ val entries: Array<Entry>
val source: String
- val sourceChecksum: String
fun getEntry(timestamp: Long): Entry {
return entries.firstOrNull { it.timestamp == timestamp }
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt
index 3b3c2b2f0..84b7e2c83 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt
@@ -21,6 +21,22 @@ data class Point(val x: Int, val y: Int) {
override fun toString(): String = prettyPrint()
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Point) return false
+
+ if (x != other.x) return false
+ if (y != other.y) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = x
+ result = 31 * result + y
+ return result
+ }
+
companion object {
fun prettyPrint(point: Point): String = "(${point.x}, ${point.y})"
}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt b/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt
index db55eda45..5830ad024 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt
@@ -73,11 +73,11 @@ data class RectF(
* @return A rectangle with the intersection coordinates
*/
fun intersection(left: Float, top: Float, right: Float, bottom: Float): RectF {
- if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
- var intersectionLeft = 0f
- var intersectionTop = 0f
- var intersectionRight = 0f
- var intersectionBottom = 0f
+ if (this.left < right && left < this.right && this.top <= bottom && top <= this.bottom) {
+ var intersectionLeft = this.left
+ var intersectionTop = this.top
+ var intersectionRight = this.right
+ var intersectionBottom = this.bottom
if (this.left < left) {
intersectionLeft = left
@@ -111,6 +111,26 @@ data class RectF(
override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is RectF) return false
+
+ if (left != other.left) return false
+ if (top != other.top) return false
+ if (right != other.right) return false
+ if (bottom != other.bottom) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = left.hashCode()
+ result = 31 * result + top.hashCode()
+ result = 31 * result + right.hashCode()
+ result = 31 * result + bottom.hashCode()
+ return result
+ }
+
companion object {
val EMPTY = RectF()
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Region.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Region.kt
index 0bacfe9fe..b6c173478 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Region.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Region.kt
@@ -39,6 +39,22 @@ class Region(val rects: Array<Rect>) : Rect(
override fun prettyPrint(): String = rects.joinToString(", ") { it.prettyPrint() }
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Region) return false
+ if (!super.equals(other)) return false
+
+ if (!rects.contentEquals(other.rects)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + rects.contentHashCode()
+ return result
+ }
+
companion object {
val EMPTY = Region()
}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Bounds.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Size.kt
index 52b838488..d5bd20aac 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Bounds.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/Size.kt
@@ -16,22 +16,19 @@
package com.android.server.wm.traces.common
-open class Bounds(val width: Int, val height: Int) {
+open class Size(val width: Int, val height: Int) {
open val isEmpty: Boolean
get() = height == 0 || width == 0
val isNotEmpty: Boolean
get() = !isEmpty
- val size: Bounds
- get() = Bounds(width, height)
-
open fun prettyPrint(): String = prettyPrint(this)
override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
override fun equals(other: Any?): Boolean =
- other is Bounds &&
+ other is Size &&
other.height == height &&
other.width == width
@@ -42,8 +39,8 @@ open class Bounds(val width: Int, val height: Int) {
}
companion object {
- val EMPTY: Bounds = Bounds(0, 0)
+ val EMPTY: Size = Size(0, 0)
- fun prettyPrint(bounds: Bounds): String = "${bounds.width} x ${bounds.height}"
+ fun prettyPrint(bounds: Size): String = "${bounds.width} x ${bounds.height}"
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt b/libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt
new file mode 100644
index 000000000..cd8e3161a
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+/**
+ * The utility class to wait a condition with customized options.
+ * The default retry policy is 5 times with interval 1 second.
+ *
+ * @param <T> The type of the object to validate.
+ *
+ * <p>Sample:</p>
+ * <pre>
+ * // Simple case.
+ * if (Condition.waitFor("true value", () -> true)) {
+ * println("Success");
+ * }
+ * // Wait for customized result with customized validation.
+ * String result = WaitForCondition.Builder(supplier = () -> "Result string")
+ * .withCondition(str -> str.equals("Expected string"))
+ * .withRetryIntervalMs(500)
+ * .withRetryLimit(3)
+ * .onFailure(str -> println("Failed on " + str)))
+ * .build()
+ * .waitFor()
+ * </pre>
+
+ * @param condition If it returns true, that means the condition is satisfied.
+ */
+class WaitCondition<T> private constructor(
+ private val supplier: () -> T,
+ private val condition: Condition<T>,
+ private val retryLimit: Int,
+ private val onLog: ((String) -> Unit)?,
+ private val onFailure: ((T) -> Any)?,
+ private val onRetry: ((T) -> Any)?,
+ private val onSuccess: ((T) -> Any)?
+) {
+ /**
+ * @return `false` if the condition does not satisfy within the time limit.
+ */
+ fun waitFor(): Boolean {
+ onLog?.invoke("***Waiting for $condition")
+ var currState: T? = null
+ for (i in 0..retryLimit) {
+ currState = supplier.invoke()
+ if (condition.isSatisfied(currState)) {
+ onLog?.invoke("***Waiting for $condition ... Success!")
+ onSuccess?.invoke(currState)
+ return true
+ } else {
+ val detailedMessage = condition.getMessage(currState)
+ onLog?.invoke("***Waiting for $detailedMessage... retry=${i + 1}")
+ if (i < retryLimit) {
+ onRetry?.invoke(currState)
+ }
+ }
+ }
+
+ val detailedMessage = if (currState != null) {
+ condition.getMessage(currState)
+ } else {
+ condition.toString()
+ }
+ onLog?.invoke("***Waiting for $detailedMessage ... Failed!")
+ if (onFailure != null) {
+ require(currState != null) { "Missing last result for failure notification" }
+ onFailure.invoke(currState)
+ }
+ return false
+ }
+
+ class Builder<T>(
+ private val supplier: () -> T,
+ private var retryLimit: Int
+ ) {
+ private val conditions = mutableListOf<Condition<T>>()
+ private var onFailure: ((T) -> Any)? = null
+ private var onRetry: ((T) -> Any)? = null
+ private var onSuccess: ((T) -> Any)? = null
+ private var onLog: ((String) -> Unit)? = null
+
+ fun withCondition(condition: Condition<T>) =
+ apply { conditions.add(condition) }
+
+ fun withCondition(message: String, condition: (T) -> Boolean) =
+ apply { withCondition(Condition(message, condition)) }
+
+ private fun spreadConditionList(): List<Condition<T>> =
+ conditions.flatMap {
+ if (it is ConditionList<T>) {
+ it.conditions
+ } else {
+ listOf(it)
+ }
+ }
+
+ /**
+ * Executes the action when the condition does not satisfy within the time limit. The passed
+ * object to the consumer will be the last result from the supplier.
+ */
+ fun onFailure(onFailure: (T) -> Any): Builder<T> =
+ apply { this.onFailure = onFailure }
+
+ fun onLog(onLog: (String) -> Unit): Builder<T> =
+ apply { this.onLog = onLog }
+
+ fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> =
+ apply { this.onRetry = onRetry }
+
+ fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> =
+ apply { this.onSuccess = onRetry }
+
+ fun build(): WaitCondition<T> =
+ WaitCondition(supplier, ConditionList(spreadConditionList()), retryLimit,
+ onLog, onFailure, onRetry, onSuccess)
+ }
+
+ companion object {
+ // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary
+ // constant time, currently keep the default as 5*1s because most of the original code
+ // uses it, and some tests might be sensitive to the waiting interval.
+ const val DEFAULT_RETRY_LIMIT = 10
+ const val DEFAULT_RETRY_INTERVAL_MS = 500L
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt
new file mode 100644
index 000000000..030ad1806
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common
+
+import com.android.server.wm.traces.common.layers.Layer
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagSet
+import com.android.server.wm.traces.common.service.PlatformConsts
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.common.windowmanager.windows.WindowState
+
+object WindowManagerConditionsFactory {
+ private val navBarWindowName = FlickerComponentName.NAV_BAR.toWindowName()
+ private val navBarLayerName = FlickerComponentName.NAV_BAR.toLayerName()
+ private val statusBarWindowName = FlickerComponentName.STATUS_BAR.toWindowName()
+ private val statusBarLayerName = FlickerComponentName.STATUS_BAR.toLayerName()
+
+ /**
+ * Condition to check if the nav bar window is visible
+ */
+ fun isNavBarVisible(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ ConditionList(listOf(
+ isNavBarWindowVisible(), isNavBarLayerVisible(), isNavBarLayerOpaque()))
+
+ /**
+ * Condition to check if the nav bar window is visible
+ */
+ fun isNavBarWindowVisible(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isNavBarWindowVisible") {
+ it.wmState.isWindowVisible(navBarWindowName)
+ }
+
+ /**
+ * Condition to check if the nav bar layer is visible
+ */
+ fun isNavBarLayerVisible(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ isLayerVisible(navBarLayerName)
+
+ /**
+ * Condition to check if the nav bar layer is opaque
+ */
+ fun isNavBarLayerOpaque(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isNavBarLayerOpaque") {
+ it.layerState.getLayerWithBuffer(navBarLayerName)
+ ?.color?.a ?: 0f == 1f
+ }
+
+ /**
+ * Condition to check if the status bar window is visible
+ */
+ fun isStatusBarVisible(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ ConditionList(listOf(
+ isStatusBarWindowVisible(), isStatusBarLayerVisible(), isStatusBarLayerOpaque()))
+
+ /**
+ * Condition to check if the nav bar window is visible
+ */
+ fun isStatusBarWindowVisible():
+ Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isStatusBarWindowVisible") {
+ it.wmState.isWindowVisible(statusBarWindowName)
+ }
+
+ /**
+ * Condition to check if the nav bar layer is visible
+ */
+ fun isStatusBarLayerVisible(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ isLayerVisible(statusBarLayerName)
+
+ /**
+ * Condition to check if the nav bar layer is opaque
+ */
+ fun isStatusBarLayerOpaque(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isStatusBarLayerOpaque") {
+ it.layerState.getLayerWithBuffer(statusBarLayerName)
+ ?.color?.a ?: 0f == 1f
+ }
+
+ fun isHomeActivityVisible(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isHomeActivityVisible") {
+ it.wmState.homeActivity?.isVisible == true
+ }
+
+ fun isAppTransitionIdle(
+ displayId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isAppTransitionIdle[$displayId]") {
+ it.wmState.getDisplay(displayId)
+ ?.appTransitionState == WindowManagerState.APP_STATE_IDLE
+ }
+
+ fun containsActivity(
+ component: FlickerComponentName
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("containsActivity[${component.toActivityName()}]") {
+ it.wmState.containsActivity(component.toActivityName())
+ }
+
+ fun containsWindow(
+ component: FlickerComponentName
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("containsWindow[${component.toWindowName()}]") {
+ it.wmState.containsWindow(component.toWindowName())
+ }
+
+ fun isWindowSurfaceShown(
+ windowName: String
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isWindowSurfaceShown[$windowName]") {
+ it.wmState.isWindowSurfaceShown(windowName)
+ }
+
+ fun isWindowSurfaceShown(
+ component: FlickerComponentName
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ isWindowSurfaceShown(component.toWindowName())
+
+ fun isActivityVisible(
+ component: FlickerComponentName
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isActivityVisible") {
+ it.wmState.isActivityVisible(component.toActivityName())
+ }
+
+ fun isWMStateComplete(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isWMStateComplete") {
+ it.wmState.isComplete()
+ }
+
+ fun hasRotation(
+ expectedRotation: Int,
+ displayId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> {
+ val hasRotationCondition = Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>>(
+ "hasRotation[$expectedRotation, display=$displayId]") {
+ val currRotation = it.wmState.getRotation(displayId)
+ currRotation == expectedRotation
+ }
+ return ConditionList(listOf(
+ hasRotationCondition,
+ isLayerVisible(FlickerComponentName.ROTATION).negate(),
+ isLayerVisible(FlickerComponentName.BACK_SURFACE).negate(),
+ hasLayersAnimating().negate()
+ ))
+ }
+
+ fun isLayerVisible(
+ layerName: String
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isLayerVisible[$layerName]") {
+ it.layerState.isVisible(layerName)
+ }
+
+ fun isLayerVisible(
+ layerId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isLayerVisible[$layerId]") {
+ it.layerState.getLayerById(layerId)?.isVisible ?: false
+ }
+
+ fun isLayerColorAlphaOne(
+ component: FlickerComponentName
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isLayerColorAlphaOne[${component.toLayerName()}]") {
+ val layers = it.layerState.getVisibleLayersByName(component)
+ layers.any { layer -> layer.color.a == 1.0f }
+ }
+
+ fun isLayerColorAlphaOne(
+ layerId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isLayerColorAlphaOne[$layerId]") {
+ val layer = it.layerState.getLayerById(layerId)
+ layer?.color?.a == 1.0f
+ }
+
+ fun isLayerTransformFlagSet(
+ component: FlickerComponentName,
+ transform: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isLayerTransformFlagSet[" +
+ "${component.toLayerName()},transform=$transform]") {
+ val layers = it.layerState.getVisibleLayersByName(component)
+ layers.any { layer -> isTransformFlagSet(layer, transform) }
+ }
+
+ fun isLayerTransformFlagSet(
+ layerId: Int,
+ transform: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isLayerTransformFlagSet[$layerId, $transform]") {
+ val layer = it.layerState.getLayerById(layerId)
+ layer?.transform?.type?.isFlagSet(transform) ?: false
+ }
+
+ fun isLayerTransformIdentity(
+ layerId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ ConditionList(listOf(
+ isLayerTransformFlagSet(layerId, Transform.SCALE_VAL).negate(),
+ isLayerTransformFlagSet(layerId, Transform.TRANSLATE_VAL).negate(),
+ isLayerTransformFlagSet(layerId, Transform.ROTATE_VAL).negate()
+ ))
+
+ private fun isTransformFlagSet(layer: Layer, transform: Int): Boolean =
+ layer.transform.type?.isFlagSet(transform) ?: false
+
+ fun LayerTraceEntry.getVisibleLayersByName(
+ component: FlickerComponentName
+ ): List<Layer> = visibleLayers.filter { it.name.contains(component.toLayerName()) }
+
+ fun isLayerVisible(
+ component: FlickerComponentName
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ isLayerVisible(component.toLayerName())
+
+ fun hasLayersAnimating(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("hasLayersAnimating") {
+ it.layerState.isAnimating()
+ }
+
+ fun isPipWindowLayerSizeMatch(
+ layerId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isPipWindowLayerSizeMatch") {
+ val pipWindow = it.wmState.pinnedWindows.firstOrNull { it.layerId == layerId }
+ ?: error("Unable to find window with layerId $layerId")
+ val windowHeight = pipWindow.frame.height.toFloat()
+ val windowWidth = pipWindow.frame.width.toFloat()
+
+ val pipLayer = it.layerState.getLayerById(layerId)
+ val layerHeight = pipLayer?.sourceBounds?.height
+ ?: error("Unable to find layer with id $layerId")
+ val layerWidth = pipLayer.sourceBounds.width
+
+ windowHeight == layerHeight && windowWidth == layerWidth
+ }
+
+ fun hasPipWindow(): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("hasPipWindow") {
+ it.wmState.hasPipWindow()
+ }
+
+ fun isImeShown(
+ displayId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ ConditionList(listOf(
+ isImeOnDisplay(displayId),
+ isLayerVisible(FlickerComponentName.IME),
+ isImeSurfaceShown(),
+ isWindowSurfaceShown(FlickerComponentName.IME.toWindowName())
+ ))
+
+ private fun isImeOnDisplay(
+ displayId: Int
+ ): Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isImeOnDisplay[$displayId]") {
+ it.wmState.inputMethodWindowState?.displayId == displayId
+ }
+
+ private fun isImeSurfaceShown():
+ Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("isImeSurfaceShown") {
+ it.wmState.inputMethodWindowState?.isSurfaceShown == true
+ }
+
+ fun isAppLaunchEnded(taskId: Int):
+ Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ Condition("containsVisibleAppLaunchWindow[$taskId]") { dump ->
+ val windowStates = dump.wmState.getRootTask(taskId)?.activities?.flatMap {
+ it.children.filterIsInstance<WindowState>()
+ }
+ windowStates != null && windowStates.none { window ->
+ window.attributes.type == PlatformConsts.TYPE_APPLICATION_STARTING &&
+ window.isVisible
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt b/libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt
new file mode 100644
index 000000000..7663b9373
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.errors
+
+/**
+ * Flicker Error identified in a WindowManager or SurfaceFlinger trace
+ * @param stacktrace Stacktrace to identify source of errors
+ * @param message Message to explain error briefly
+ * @param layerId The layer which the error is associated with
+ * @param windowToken The window which the error is associated with
+ * @param taskId The task which the error is associated with
+ * @param assertionName The class name of the assertion that generated the error
+ */
+data class Error(
+ val stacktrace: String,
+ val message: String,
+ val layerId: Int = 0,
+ val windowToken: String = "",
+ val taskId: Int = 0,
+ val assertionName: String = ""
+) \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt
new file mode 100644
index 000000000..0d285e06a
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt
@@ -0,0 +1,33 @@
+package com.android.server.wm.traces.common.errors
+
+import com.android.server.wm.traces.common.ITraceEntry
+import com.android.server.wm.traces.common.prettyTimestamp
+
+/**
+ * A state at a particular time within a trace that holds a list of errors there may be.
+ * @param errors Errors contained in the state
+ * @param _timestamp Timestamp of this state
+ */
+class ErrorState(
+ val errors: Array<Error>,
+ _timestamp: String
+) : ITraceEntry {
+ override val timestamp: Long = _timestamp.toLong()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ErrorState) return false
+ if (timestamp != other.timestamp) return false
+ if (errors.contentEquals(other.errors)) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = timestamp.hashCode()
+ result = 31 * result + errors.contentDeepHashCode()
+ return result
+ }
+
+ override fun toString(): String = "FlickerErrorState(" +
+ "timestamp=${prettyTimestamp(timestamp)}, numberOfErrors=${errors.size})"
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt
new file mode 100644
index 000000000..e851e1d19
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.errors
+
+import com.android.server.wm.traces.common.ITrace
+
+/**
+ * Represents all the states with errors in an entire trace.
+ * @param entries States with errors contained in this trace
+ * @param source Source of the trace
+ */
+data class ErrorTrace(
+ override val entries: Array<ErrorState>,
+ override val source: String
+) : ITrace<ErrorState>,
+ List<ErrorState> by entries.toList() {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ErrorTrace) return false
+ if (entries != other.entries) return false
+ return true
+ }
+
+ override fun hashCode(): Int = entries.contentDeepHashCode()
+
+ override fun toString(): String = "FlickerErrorTrace(First: ${entries.first()}," +
+ "End: ${entries.last()})"
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt
new file mode 100644
index 000000000..1dfa94e71
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.layers
+
+import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.Size
+
+/**
+ * Representation of a Display in the SF trace
+ */
+data class Display(
+ val id: ULong,
+ val name: String,
+ val layerStackId: Int,
+ val size: Size,
+ val layerStackSpace: Rect,
+ val transform: Transform
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Display) return false
+
+ if (id != other.id) return false
+ if (name != other.name) return false
+ if (layerStackId != other.layerStackId) return false
+ if (size != other.size) return false
+ if (layerStackSpace != other.layerStackSpace) return false
+ if (transform != other.transform) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = id.toInt()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + layerStackId
+ result = 31 * result + size.hashCode()
+ result = 31 * result + layerStackSpace.hashCode()
+ result = 31 * result + transform.hashCode()
+ return result
+ }
+
+ companion object {
+ val EMPTY = Display(
+ id = 0.toULong(),
+ name = "EMPTY",
+ layerStackId = -1,
+ size = Size.EMPTY,
+ layerStackSpace = Rect.EMPTY,
+ transform = Transform.EMPTY
+ )
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt
index 2dd763003..ddff3ebc8 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt
@@ -19,8 +19,9 @@ package com.android.server.wm.traces.common.layers
import com.android.server.wm.traces.common.Buffer
import com.android.server.wm.traces.common.Color
import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.Region
import com.android.server.wm.traces.common.RectF
+import com.android.server.wm.traces.common.Region
+import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagSet
/**
* Represents a single layer with links to its parent and child layers.
@@ -29,7 +30,7 @@ import com.android.server.wm.traces.common.RectF
* access internal Java/Android functionality
*
**/
-open class Layer(
+data class Layer(
val name: String,
val id: Int,
val parentId: Int,
@@ -37,15 +38,15 @@ open class Layer(
val visibleRegion: Region?,
val activeBuffer: Buffer,
val flags: Int,
- _bounds: RectF?,
+ val bounds: RectF,
val color: Color,
- _isOpaque: Boolean,
+ private val _isOpaque: Boolean,
val shadowRadius: Float,
val cornerRadius: Float,
val type: String,
- _screenBounds: RectF?,
+ private val _screenBounds: RectF?,
val transform: Transform,
- _sourceBounds: RectF?,
+ val sourceBounds: RectF,
val currFrame: Long,
val effectiveScalingMode: Int,
val bufferTransform: Transform,
@@ -57,7 +58,8 @@ open class Layer(
val isRelativeOf: Boolean,
val zOrderRelativeOfId: Int
) {
- lateinit var parent: Layer
+ val stableId: String = "$type $id $name"
+ var parent: Layer? = null
var zOrderRelativeOf: Layer? = null
var zOrderRelativeParentOf: Int = 0
@@ -66,22 +68,32 @@ open class Layer(
*
* @return
*/
- val isRootLayer: Boolean
- get() {
- return !::parent.isInitialized
- }
-
- val children = mutableListOf<Layer>()
- val occludedBy = mutableListOf<Layer>()
- val partiallyOccludedBy = mutableListOf<Layer>()
- val coveredBy = mutableListOf<Layer>()
-
- fun addChild(childLayer: Layer) {
- children.add(childLayer)
- }
-
- val bounds: RectF = _bounds ?: RectF.EMPTY
- val sourceBounds: RectF = _sourceBounds ?: RectF.EMPTY
+ val isRootLayer: Boolean get() = parent == null
+
+ private val _children = mutableListOf<Layer>()
+ private val _occludedBy = mutableListOf<Layer>()
+ private val _partiallyOccludedBy = mutableListOf<Layer>()
+ private val _coveredBy = mutableListOf<Layer>()
+ val children: Array<Layer>
+ get() = _children.toTypedArray()
+ val occludedBy: Array<Layer>
+ get() = _occludedBy.toTypedArray()
+ val partiallyOccludedBy: Array<Layer>
+ get() = _partiallyOccludedBy.toTypedArray()
+ val coveredBy: Array<Layer>
+ get() = _coveredBy.toTypedArray()
+ var isMissing: Boolean = false
+ internal set
+
+ val isScaling: Boolean
+ get() = isTransformFlagSet(Transform.SCALE_VAL)
+ val isTranslating: Boolean
+ get() = isTransformFlagSet(Transform.TRANSLATE_VAL)
+ val isRotating: Boolean
+ get() = isTransformFlagSet(Transform.ROTATE_VAL)
+
+ private fun isTransformFlagSet(transform: Int): Boolean =
+ this.transform.type?.isFlagSet(transform) ?: false
/**
* Checks if the layer's active buffer is empty
@@ -135,10 +147,7 @@ open class Layer(
*
* @return
*/
- val fillsColor: Boolean
- get() {
- return color.isNotEmpty
- }
+ val fillsColor: Boolean get() = color.isNotEmpty
/**
* Checks if the [Layer] draws a shadow
@@ -209,19 +218,13 @@ open class Layer(
val isEffectLayer: Boolean get() = type == "EffectLayer"
/**
- * Checks if the [Layer] is not visible
- *
- * @return
- */
- val isInvisible: Boolean get() = !isVisible
-
- /**
* Checks if the [Layer] is hidden by its parent
*
* @return
*/
val isHiddenByParent: Boolean
- get() = !isRootLayer && (parent.isHiddenByPolicy || parent.isHiddenByParent)
+ get() = !isRootLayer &&
+ (parent?.isHiddenByPolicy == true || parent?.isHiddenByParent == true)
/**
* Gets a description of why the layer is (in)visible
@@ -234,7 +237,7 @@ open class Layer(
isVisible -> ""
isContainerLayer -> "ContainerLayer"
isHiddenByPolicy -> "Flag is hidden"
- isHiddenByParent -> "Hidden by parent ${parent.name}"
+ isHiddenByParent -> "Hidden by parent ${parent?.name}"
isBufferLayer && isActiveBufferEmpty -> "Buffer is empty"
color.isEmpty -> "Alpha is 0"
crop?.isEmpty ?: false -> "Crop is 0x0"
@@ -243,8 +246,8 @@ open class Layer(
isRelativeOf && zOrderRelativeOf == null -> "RelativeOf layer has been removed"
isEffectLayer && !fillsColor && !drawsShadows && !hasBlur ->
"Effect layer does not have color fill, shadow or blur"
- occludedBy.isNotEmpty() -> {
- val occludedByIds = occludedBy.joinToString(", ") { it.id.toString() }
+ _occludedBy.isNotEmpty() -> {
+ val occludedByIds = _occludedBy.joinToString(", ") { it.id.toString() }
"Layer is occluded by: $occludedByIds"
}
visibleRegion?.isEmpty ?: false ->
@@ -259,6 +262,18 @@ open class Layer(
else -> transform.apply(bounds)
}
+ val absoluteZ: String
+ get() {
+ val zOrderRelativeOf = zOrderRelativeOf
+ return buildString {
+ when {
+ zOrderRelativeOf != null -> append(zOrderRelativeOf.absoluteZ).append(",")
+ parent != null -> append(parent?.absoluteZ).append(",")
+ }
+ append(z)
+ }
+ }
+
fun contains(innerLayer: Layer): Boolean {
return if (!this.transform.isSimpleRotation || !innerLayer.transform.isSimpleRotation) {
false
@@ -267,6 +282,22 @@ open class Layer(
}
}
+ fun addChild(childLayer: Layer) {
+ _children.add(childLayer)
+ }
+
+ fun addOccludedBy(layers: Array<Layer>) {
+ _occludedBy.addAll(layers)
+ }
+
+ fun addPartiallyOccludedBy(layers: Array<Layer>) {
+ _partiallyOccludedBy.addAll(layers)
+ }
+
+ fun addCoveredBy(layers: Array<Layer>) {
+ _coveredBy.addAll(layers)
+ }
+
fun overlaps(other: Layer): Boolean =
!this.screenBounds.intersection(other.screenBounds).isEmpty
@@ -275,7 +306,7 @@ open class Layer(
append(name)
if (activeBuffer.isNotEmpty) {
- append(" buffer:${activeBuffer.width}x${activeBuffer.height}")
+ append(" buffer:$activeBuffer")
append(" frame#$currFrame")
}
@@ -286,13 +317,42 @@ open class Layer(
}
override fun equals(other: Any?): Boolean {
- return other is Layer &&
- other.parentId == this.parentId &&
- other.name == this.name &&
- other.flags == this.flags &&
- other.currFrame == this.currFrame &&
- other.activeBuffer == this.activeBuffer &&
- other.screenBounds == this.screenBounds
+ if (this === other) return true
+ if (other !is Layer) return false
+
+ if (name != other.name) return false
+ if (id != other.id) return false
+ if (parentId != other.parentId) return false
+ if (z != other.z) return false
+ if (visibleRegion != other.visibleRegion) return false
+ if (activeBuffer != other.activeBuffer) return false
+ if (flags != other.flags) return false
+ if (bounds != other.bounds) return false
+ if (color != other.color) return false
+ if (shadowRadius != other.shadowRadius) return false
+ if (cornerRadius != other.cornerRadius) return false
+ if (type != other.type) return false
+ if (transform != other.transform) return false
+ if (sourceBounds != other.sourceBounds) return false
+ if (currFrame != other.currFrame) return false
+ if (effectiveScalingMode != other.effectiveScalingMode) return false
+ if (bufferTransform != other.bufferTransform) return false
+ if (hwcCompositionType != other.hwcCompositionType) return false
+ if (hwcCrop != other.hwcCrop) return false
+ if (hwcFrame != other.hwcFrame) return false
+ if (backgroundBlurRadius != other.backgroundBlurRadius) return false
+ if (crop != other.crop) return false
+ if (isRelativeOf != other.isRelativeOf) return false
+ if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false
+ if (stableId != other.stableId) return false
+ if (parent != other.parent) return false
+ if (zOrderRelativeOf != other.zOrderRelativeOf) return false
+ if (zOrderRelativeParentOf != other.zOrderRelativeParentOf) return false
+ if (isMissing != other.isMissing) return false
+ if (isOpaque != other.isOpaque) return false
+ if (screenBounds != other.screenBounds) return false
+
+ return true
}
override fun hashCode(): Int {
@@ -300,26 +360,33 @@ open class Layer(
result = 31 * result + id
result = 31 * result + parentId
result = 31 * result + z
- result = 31 * result + visibleRegion.hashCode()
+ result = 31 * result + (visibleRegion?.hashCode() ?: 0)
result = 31 * result + activeBuffer.hashCode()
result = 31 * result + flags
result = 31 * result + bounds.hashCode()
result = 31 * result + color.hashCode()
- result = 31 * result + isOpaque.hashCode()
result = 31 * result + shadowRadius.hashCode()
result = 31 * result + cornerRadius.hashCode()
result = 31 * result + type.hashCode()
- result = 31 * result + screenBounds.hashCode()
result = 31 * result + transform.hashCode()
result = 31 * result + sourceBounds.hashCode()
result = 31 * result + currFrame.hashCode()
result = 31 * result + effectiveScalingMode
result = 31 * result + bufferTransform.hashCode()
- result = 31 * result + parent.hashCode()
- result = 31 * result + children.hashCode()
- result = 31 * result + occludedBy.hashCode()
- result = 31 * result + partiallyOccludedBy.hashCode()
- result = 31 * result + coveredBy.hashCode()
+ result = 31 * result + hwcCompositionType
+ result = 31 * result + hwcCrop.hashCode()
+ result = 31 * result + hwcFrame.hashCode()
+ result = 31 * result + backgroundBlurRadius
+ result = 31 * result + (crop?.hashCode() ?: 0)
+ result = 31 * result + isRelativeOf.hashCode()
+ result = 31 * result + zOrderRelativeOfId
+ result = 31 * result + stableId.hashCode()
+ result = 31 * result + (parent?.hashCode() ?: 0)
+ result = 31 * result + (zOrderRelativeOf?.hashCode() ?: 0)
+ result = 31 * result + zOrderRelativeParentOf
+ result = 31 * result + isMissing.hashCode()
+ result = 31 * result + isOpaque.hashCode()
+ result = 31 * result + screenBounds.hashCode()
return result
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt
index c440de9a5..47ec4bff0 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt
@@ -27,13 +27,18 @@ import com.android.server.wm.traces.common.prettyTimestamp
*
**/
open class LayerTraceEntry constructor(
- override val timestamp: Long, // hierarchical representation of layers
+ override val timestamp: Long,
val hwcBlob: String,
val where: String,
+ val displays: Array<Display>,
_rootLayers: Array<Layer>
) : ITraceEntry {
+ val isVisible = true
+ val stableId: String get() = this::class.simpleName ?: error("Unable to determine class")
+ val name: String get() = prettyTimestamp(timestamp)
+
val flattenedLayers: Array<Layer> = fillFlattenedLayers(_rootLayers)
- val rootLayers: Array<Layer> get() = flattenedLayers.filter { it.isRootLayer }.toTypedArray()
+ val children: Array<Layer> get() = flattenedLayers.filter { it.isRootLayer }.toTypedArray()
private fun fillFlattenedLayers(rootLayers: Array<Layer>): Array<Layer> {
val opaqueLayers = mutableListOf<Layer>()
@@ -79,11 +84,15 @@ open class LayerTraceEntry constructor(
val visible = layer.isVisible
if (visible) {
- layer.occludedBy.addAll(opaqueLayers
- .filter { it.contains(layer) && !it.hasRoundedCorners })
- layer.partiallyOccludedBy.addAll(
- opaqueLayers.filter { it.overlaps(layer) && it !in layer.occludedBy })
- layer.coveredBy.addAll(transparentLayers.filter { it.overlaps(layer) })
+ val occludedBy = opaqueLayers
+ .filter { it.contains(layer) && !it.hasRoundedCorners }.toTypedArray()
+ layer.addOccludedBy(occludedBy)
+ val partiallyOccludedBy = opaqueLayers
+ .filter { it.overlaps(layer) && it !in layer.occludedBy }
+ .toTypedArray()
+ layer.addPartiallyOccludedBy(partiallyOccludedBy)
+ val coveredBy = transparentLayers.filter { it.overlaps(layer) }.toTypedArray()
+ layer.addCoveredBy(coveredBy)
if (layer.isOpaque) {
opaqueLayers.add(layer)
@@ -102,13 +111,39 @@ open class LayerTraceEntry constructor(
}
}
+ fun getLayerById(layerId: Int): Layer? = this.flattenedLayers.firstOrNull { it.id == layerId }
+
+ /**
+ * Checks the transform of any layer is not a simple rotation
+ */
+ fun isAnimating(windowName: String = ""): Boolean {
+ val layers = visibleLayers.filter { it.name.contains(windowName) }
+ return layers.any { layer -> !layer.transform.isSimpleRotation }
+ }
+
/**
* Check if at least one window which matches provided window name is visible.
*/
fun isVisible(windowName: String): Boolean =
- visibleLayers.any { it.name == windowName }
+ visibleLayers.any { it.name.contains(windowName) }
+
+ fun asTrace(): LayersTrace = LayersTrace(arrayOf(this), source = "")
override fun toString(): String {
- return prettyTimestamp(timestamp)
+ return "${prettyTimestamp(timestamp)} (timestamp=$timestamp)"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is LayerTraceEntry && other.timestamp == this.timestamp
+ }
+
+ override fun hashCode(): Int {
+ var result = timestamp.hashCode()
+ result = 31 * result + hwcBlob.hashCode()
+ result = 31 * result + where.hashCode()
+ result = 31 * result + displays.contentHashCode()
+ result = 31 * result + isVisible.hashCode()
+ result = 31 * result + flattenedLayers.contentHashCode()
+ return result
}
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt
index 446678f6e..3bcd774cf 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt
@@ -21,9 +21,10 @@ package com.android.server.wm.traces.common.layers
*/
class LayerTraceEntryBuilder(
timestamp: Any,
- layers: List<Layer>,
- val hwcBlob: String = "",
- val where: String = ""
+ layers: Array<Layer>,
+ private val displays: Array<Display>,
+ private val hwcBlob: String = "",
+ private val where: String = ""
) {
// Necessary for compatibility with JS number type
private val timestamp: Long = "$timestamp".toLong()
@@ -31,7 +32,7 @@ class LayerTraceEntryBuilder(
private val orphans = mutableListOf<Layer>()
private val layers = setLayers(layers)
- private fun setLayers(layers: List<Layer>): Map<Int, Layer> {
+ private fun setLayers(layers: Array<Layer>): Map<Int, Layer> {
val result = mutableMapOf<Int, Layer>()
layers.forEach { layer ->
val id = layer.id
@@ -129,6 +130,6 @@ class LayerTraceEntryBuilder(
// Fail if we find orphan layers.
notifyOrphansLayers()
- return LayerTraceEntry(timestamp, hwcBlob, where, rootLayers.toTypedArray())
+ return LayerTraceEntry(timestamp, hwcBlob, where, displays, rootLayers.toTypedArray())
}
-} \ No newline at end of file
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt
index 5162b7cdc..988f35748 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt
@@ -27,15 +27,46 @@ import com.android.server.wm.traces.common.ITrace
* access internal Java/Android functionality
*
*/
-open class LayersTrace(
- override val entries: List<LayerTraceEntry>,
- override val source: String = "",
- override val sourceChecksum: String = ""
-) : ITrace<LayerTraceEntry>, List<LayerTraceEntry> by entries {
- constructor(entry: LayerTraceEntry): this(listOf(entry))
+data class LayersTrace(
+ override val entries: Array<LayerTraceEntry>,
+ override val source: String = ""
+) : ITrace<LayerTraceEntry>, List<LayerTraceEntry> by entries.toList() {
+ constructor(entry: LayerTraceEntry): this(arrayOf(entry))
override fun toString(): String {
return "LayersTrace(Start: ${entries.first()}, " +
"End: ${entries.last()})"
}
-} \ No newline at end of file
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is LayersTrace) return false
+
+ if (!entries.contentEquals(other.entries)) return false
+ if (source != other.source) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entries.contentHashCode()
+ result = 31 * result + source.hashCode()
+ return result
+ }
+
+ /**
+ * Split the trace by the start and end timestamp.
+ *
+ * @param from the start timestamp
+ * @param to the end timestamp
+ * @return the subtrace trace(from, to)
+ */
+ fun filter(from: Long, to: Long): LayersTrace {
+ return LayersTrace(
+ this.entries
+ .dropWhile { it.timestamp < from }
+ .dropLastWhile { it.timestamp > to }
+ .toTypedArray(),
+ source = "")
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt
index 144fbabb6..2c05628ee 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt
@@ -16,7 +16,9 @@
package com.android.server.wm.traces.common.layers
+import com.android.server.wm.traces.common.FloatFormatter
import com.android.server.wm.traces.common.RectF
+import com.android.server.wm.traces.common.service.PlatformConsts
open class Transform(val type: Int?, val matrix: Matrix) {
@@ -46,6 +48,20 @@ open class Transform(val type: Int?, val matrix: Matrix) {
return matrix.dsdx * matrix.dtdy != matrix.dtdx * matrix.dsdy
}
+ fun getRotation(): Int {
+ if (type == null) {
+ return PlatformConsts.ROTATION_0
+ }
+
+ return when {
+ type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL) -> PlatformConsts.ROTATION_0
+ type.isFlagSet(ROT_90_VAL) -> PlatformConsts.ROTATION_90
+ type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> PlatformConsts.ROTATION_180
+ type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> PlatformConsts.ROTATION_270
+ else -> PlatformConsts.ROTATION_0
+ }
+ }
+
private val typeFlags: Array<String>
get() {
if (type == null) {
@@ -100,6 +116,8 @@ open class Transform(val type: Int?, val matrix: Matrix) {
return "$transformType ${matrix.prettyPrint()}"
}
+ override fun toString(): String = prettyPrint()
+
fun apply(bounds: RectF?): RectF {
return multiplyRect(matrix, bounds ?: RectF.EMPTY)
}
@@ -116,7 +134,17 @@ open class Transform(val type: Int?, val matrix: Matrix) {
val dtdy: Float,
val ty: Float
) {
- fun prettyPrint(): String = "dsdx:$dsdx dtdx:$dtdx dsdy:$dsdy dtdy:$dtdy"
+ fun prettyPrint(): String {
+ val dsdx = FloatFormatter.format(dsdx)
+ val dtdx = FloatFormatter.format(dtdx)
+ val dsdy = FloatFormatter.format(dsdy)
+ val dtdy = FloatFormatter.format(dtdy)
+ return "dsdx:$dsdx dtdx:$dtdx dsdy:$dsdy dtdy:$dtdy"
+ }
+
+ companion object {
+ val EMPTY: Matrix = Matrix(0f, 0f, 0f, 0f, 0f, 0f)
+ }
}
private data class Vec2(val x: Float, val y: Float)
@@ -150,6 +178,8 @@ open class Transform(val type: Int?, val matrix: Matrix) {
}
companion object {
+ val EMPTY: Transform = Transform(type = null, matrix = Matrix.EMPTY)
+
/* transform type flags */
const val TRANSLATE_VAL = 0x0001
const val ROTATE_VAL = 0x0002
@@ -173,4 +203,22 @@ open class Transform(val type: Int?, val matrix: Matrix) {
return this and bits == bits
}
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Transform) return false
+
+ if (type != other.type) return false
+ if (matrix != other.matrix) return false
+ if (isSimpleRotation != other.isSimpleRotation) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = type ?: 0
+ result = 31 * result + matrix.hashCode()
+ result = 31 * result + isSimpleRotation.hashCode()
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/ITagProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/ITagProcessor.kt
new file mode 100644
index 000000000..cb049666c
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/ITagProcessor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service
+
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.tags.TagTrace
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * Interface for the WM Flicker Tag Processor component.
+ */
+interface ITagProcessor {
+ /**
+ * Adds tags to the received traces.
+ *
+ * @param wmTrace Window Manager trace
+ * @param layersTrace Surface Flinger trace
+ */
+ fun generateTags(wmTrace: WindowManagerTrace, layersTrace: LayersTrace): TagTrace
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/ITransitionAssertor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/ITransitionAssertor.kt
new file mode 100644
index 000000000..a3dcea8b9
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/ITransitionAssertor.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service
+
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * Interface for the WM Flicker Service Assertor component.
+ */
+interface ITransitionAssertor {
+ /**
+ * Analyzes a [WindowManagerTrace] and/or a [LayersTrace] trace to detect flickers.
+ *
+ * @param tag Tag for the transition
+ * @param wmTrace Window Manager trace
+ * @param layersTrace Surface Flinger trace
+ * @return An error trace
+ */
+ fun analyze(tag: Tag, wmTrace: WindowManagerTrace, layersTrace: LayersTrace): ErrorTrace
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt
new file mode 100644
index 000000000..0a13329cc
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service
+
+object PlatformConsts {
+ /**
+ * The default Display id, which is the id of the primary display assuming there is one.
+ *
+ * Duplicated from [Display.DEFAULT_DISPLAY] because this class is used by JVM and KotlinJS
+ */
+ const val DEFAULT_DISPLAY = 0
+
+ /**
+ * Window type: an application window that serves as the "base" window
+ * of the overall application
+ *
+ * Duplicated from [WindowManager.LayoutParams.TYPE_BASE_APPLICATION] because this class
+ * is used by JVM and KotlinJS
+ */
+ const val TYPE_BASE_APPLICATION = 1
+
+ /**
+ * Window type: special application window that is displayed while the
+ * application is starting
+ *
+ * Duplicated from [WindowManager.LayoutParams.TYPE_APPLICATION_STARTING] because this class
+ * is used by JVM and KotlinJS
+ */
+ const val TYPE_APPLICATION_STARTING = 3
+
+ /**
+ * Rotation constant: 0 degree rotation (natural orientation)
+ *
+ * Duplicated from [Surface.ROTATION_0] because this class is used by JVM and KotlinJS
+ */
+ const val ROTATION_0 = 0
+
+ /**
+ * Rotation constant: 90 degree rotation.
+ *
+ * Duplicated from [Surface.ROTATION_90] because this class is used by JVM and KotlinJS
+ */
+ const val ROTATION_90 = 1
+
+ /**
+ * Rotation constant: 180 degree rotation.
+ *
+ * Duplicated from [Surface.ROTATION_180] because this class is used by JVM and KotlinJS
+ */
+ const val ROTATION_180 = 2
+
+ /**
+ * Rotation constant: 270 degree rotation.
+ *
+ * Duplicated from [Surface.ROTATION_270] because this class is used by JVM and KotlinJS
+ */
+ const val ROTATION_270 = 3
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/TaggingEngine.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/TaggingEngine.kt
new file mode 100644
index 000000000..ef46d8e72
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/TaggingEngine.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service
+
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.service.processors.AppCloseProcessor
+import com.android.server.wm.traces.common.service.processors.AppLaunchProcessor
+import com.android.server.wm.traces.common.service.processors.ImeAppearProcessor
+import com.android.server.wm.traces.common.service.processors.ImeDisappearProcessor
+import com.android.server.wm.traces.common.service.processors.PipEnterProcessor
+import com.android.server.wm.traces.common.service.processors.PipExitProcessor
+import com.android.server.wm.traces.common.service.processors.PipExpandProcessor
+import com.android.server.wm.traces.common.service.processors.PipResizeProcessor
+import com.android.server.wm.traces.common.service.processors.RotationProcessor
+import com.android.server.wm.traces.common.tags.TagState
+import com.android.server.wm.traces.common.tags.TagTrace
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * Invokes all concrete tag producers and writes to a .winscope file
+ *
+ * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
+ *
+ * The traces are passed as constructor arguments due to name mangling in KotlinJS
+ *
+ * @param logger Platform dependent function for logging
+ * @param wmTrace WindowManager trace
+ * @param layersTrace SurfaceFlinger trace
+ */
+class TaggingEngine(
+ private val wmTrace: WindowManagerTrace,
+ private val layersTrace: LayersTrace,
+ private val logger: (String) -> Unit
+) {
+ private val transitions = listOf(
+ // TODO: Keep adding new transition processors to invoke
+ RotationProcessor(logger),
+ AppLaunchProcessor(logger),
+ AppCloseProcessor(logger),
+ ImeAppearProcessor(logger),
+ ImeDisappearProcessor(logger),
+ PipEnterProcessor(logger),
+ PipResizeProcessor(logger),
+ PipExpandProcessor(logger),
+ PipExitProcessor(logger)
+ )
+
+ /**
+ * Generate tags denoting start and end points for all [transitions] within traces
+ */
+ fun run(): TagTrace {
+ val allStates = transitions.flatMap {
+ logger.invoke("Generating tags for ${it::class.simpleName}")
+ it.generateTags(wmTrace, layersTrace).entries.asList()
+ }
+
+ /**
+ * Ensure all tag states with the same timestamp are merged
+ */
+ val tagStates = allStates.distinct()
+ .groupBy({ it.timestamp }, { it.tags.asList() })
+ .mapValues { (key, value) -> TagState(key.toString(), value.flatten().toTypedArray()) }
+ .values.toTypedArray()
+
+ return TagTrace(tagStates, source = "")
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppCloseProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppCloseProcessor.kt
new file mode 100644
index 000000000..7b3ffffc7
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppCloseProcessor.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformFlagSet
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformIdentity
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.service.PlatformConsts.TYPE_APPLICATION_STARTING
+import com.android.server.wm.traces.common.service.PlatformConsts.TYPE_BASE_APPLICATION
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.common.windowmanager.windows.WindowState
+
+/**
+ * This processor creates tags when an app is closed.
+ * @param logger logs by invoking any event messages
+ */
+class AppCloseProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.APP_CLOSE
+ private val areLayersAnimating = WindowManagerConditionsFactory.hasLayersAnimating()
+ private val wmStateIdle = WindowManagerConditionsFactory
+ .isAppTransitionIdle(/* default display */ 0)
+ private val wmStateComplete = WindowManagerConditionsFactory.isWMStateComplete()
+ private val translatingWindows =
+ HashMap<String, DeviceStateDump<WindowManagerState, LayerTraceEntry>>()
+
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ RetrieveClosingAppLayerId(tags)
+
+ /**
+ * Initial FSM state that passes the current app launch activity if any to the next state.
+ * Closing app is also not transforming and has transform identity
+ */
+ inner class RetrieveClosingAppLayerId(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val isStableState = wmStateIdle.isSatisfied(current) ||
+ wmStateComplete.isSatisfied(current) ||
+ areLayersAnimating.negate().isSatisfied(current)
+ val currentAppWindows = current.wmState.rootTasks.flatMap { task ->
+ // No app launch activities and only resuming activities
+ val activities = task.activities.filter { activity ->
+ activity.state == "RESUMED" && activity.isVisible &&
+ activity.children.filterIsInstance<WindowState>().none { window ->
+ window.attributes.type == TYPE_APPLICATION_STARTING
+ }
+ }
+ activities.flatMap { activity ->
+ activity.children.filterIsInstance<WindowState>().filter { window ->
+ window.isVisible && window.attributes.type == TYPE_BASE_APPLICATION
+ }
+ }
+ }
+
+ // Only one closing app. This processor ignores app pairs situations.
+ if (currentAppWindows.size == 1 && isStableState) {
+ val isNotTransforming = isLayerTransformIdentity(currentAppWindows.first().layerId)
+ .isSatisfied(current)
+ if (isNotTransforming) {
+ return WaitLayerAnimationComplete(tags, currentAppWindows.first())
+ }
+ }
+ return this
+ }
+ }
+
+ /**
+ * FSM State that waits until the closing app has finished and stopped transforming.
+ */
+ inner class WaitLayerAnimationComplete(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val appWindow: WindowState
+ ) : BaseState(tags) {
+ private val layerId = appWindow.layerId
+ private val isTranslating = isLayerTransformFlagSet(layerId, Transform.TRANSLATE_VAL)
+
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+ val startedTransforming = isTranslating.isSatisfied(current) &&
+ isTranslating.negate().isSatisfied(previous)
+ val isStableState = wmStateIdle.isSatisfied(current) ||
+ wmStateComplete.isSatisfied(current) ||
+ areLayersAnimating.negate().isSatisfied(current)
+
+ val finishedClosing = isLayerTransformIdentity(layerId).isSatisfied(current) &&
+ current.layerState.getLayerById(layerId)?.isHiddenByParent ?: false
+
+ if (startedTransforming) {
+ translatingWindows[appWindow.token] = current
+ } else if (finishedClosing && isStableState) {
+ val deviceStateDump = translatingWindows[appWindow.token]
+ if (deviceStateDump != null) {
+ addStartTransitionTag(deviceStateDump, transition,
+ layerId = layerId,
+ windowToken = appWindow.token
+ )
+ addEndTransitionTag(current, transition,
+ layerId = layerId,
+ windowToken = appWindow.token
+ )
+ return RetrieveClosingAppLayerId(tags)
+ }
+ }
+ return this
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppLaunchProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppLaunchProcessor.kt
new file mode 100644
index 000000000..cb759548f
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/AppLaunchProcessor.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * This processor creates tags when an app is launched.
+ * @param logger logs by invoking any event messages
+ */
+class AppLaunchProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.APP_LAUNCH
+ private val windowsBecomeVisible =
+ HashMap<Int, DeviceStateDump<WindowManagerState, LayerTraceEntry>>()
+
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitUntilWindowIsInVisibleActivity(tags)
+
+ /**
+ * FSM state that stores any newly visible window activities (start tag)
+ * and when their layers stop scaling (end tag).
+ */
+ inner class WaitUntilWindowIsInVisibleActivity(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+ val prevVisibleWindows = previous.wmState.visibleWindows
+ val newlyVisibleWindows = current.wmState.visibleWindows.filterNot { window ->
+ prevVisibleWindows.any { it.token == window.token }
+ }
+
+ // Wait until layer is no longer scaling
+ val appLaunchedLayers = windowsBecomeVisible.filterKeys { layerId ->
+ val currDumpLayer = current.layerState.getLayerById(layerId)
+ (previous.layerState.getLayerById(layerId)?.isScaling == true &&
+ currDumpLayer?.isScaling == false)
+ }
+
+ // Only want to tag when one app is being launched.
+ // Other scenarios like app pairs enter are ignored.
+ if (newlyVisibleWindows.size == 1) {
+ windowsBecomeVisible[newlyVisibleWindows.first().layerId] = previous
+ } else if (appLaunchedLayers.isNotEmpty()) {
+ val firstDump = appLaunchedLayers.entries.first()
+ val layerId = firstDump.key
+ addStartTransitionTag(firstDump.value, transition,
+ layerId = layerId,
+ timestamp = firstDump.value.layerState.timestamp
+ )
+ addEndTransitionTag(current, transition,
+ layerId = layerId,
+ timestamp = current.layerState.timestamp
+ )
+ windowsBecomeVisible.clear()
+ }
+ return this
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/BaseFsmState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/BaseFsmState.kt
new file mode 100644
index 000000000..2b7bb3b7f
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/BaseFsmState.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * Base state for the FSM, check if there are more WM and SF states to process
+ * and ensure there is always a 1:1 correspondence between start and end tags.
+ * If the location of the end of the transition wasn't found, add an end tag at end of trace.
+ */
+abstract class BaseFsmState(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ internal val logger: (String) -> Unit,
+ internal val transition: Transition
+) : FSMState(tags) {
+ protected abstract fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState
+
+ override fun process(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>?
+ ): FSMState? {
+ return if (next == null) {
+ // last state
+ val timestamp = current.layerState.timestamp
+ logger.invoke("($timestamp) Trace has reached the end")
+ if (hasOpenTag()) {
+ logger.invoke("($timestamp) Has an open tag, closing it on the last SF state")
+ addEndTransitionTag(current, transition)
+ }
+ null
+ } else {
+ doProcessState(previous, current, next)
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/FSMState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/FSMState.kt
new file mode 100644
index 000000000..6c661ae44
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/FSMState.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Represents the Finite State Machine used by tagging processors and implements adding start and
+ * end tags.
+ * @param tags represents the map of timestamps associated with tag(s).
+ */
+abstract class FSMState(protected val tags: MutableMap<Long, MutableList<Tag>>) {
+ abstract fun process(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>?
+ ): FSMState?
+
+ protected fun addStartTransitionTag(
+ state: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ transition: Transition,
+ layerId: Int = 0,
+ windowToken: String = "",
+ taskId: Int = 0,
+ timestamp: Long = min(state.wmState.timestamp, state.layerState.timestamp)
+ ) {
+ val tagId = ++lastTagId
+ val startTag = Tag(id = tagId, transition, isStartTag = true, layerId = layerId,
+ windowToken = windowToken, taskId = taskId)
+ if (!tags.containsKey(timestamp)) {
+ tags[timestamp] = mutableListOf()
+ }
+ tags.getValue(timestamp).add(startTag)
+ }
+
+ protected fun addEndTransitionTag(
+ state: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ transition: Transition,
+ layerId: Int = 0,
+ windowToken: String = "",
+ taskId: Int = 0,
+ timestamp: Long = max(state.wmState.timestamp, state.layerState.timestamp)
+ ) {
+ val endTag = Tag(id = lastTagId, transition, isStartTag = false, layerId = layerId,
+ windowToken = windowToken, taskId = taskId)
+ if (!tags.containsKey(timestamp)) {
+ tags[timestamp] = mutableListOf()
+ }
+ tags.getValue(timestamp).add(endTag)
+ }
+
+ protected fun hasOpenTag() = tags.values.flatten().size % 2 != 0
+
+ companion object {
+ private var lastTagId = -1
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeAppearProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeAppearProcessor.kt
new file mode 100644
index 000000000..44b5c3ca1
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeAppearProcessor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.ConditionList
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isImeShown
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerColorAlphaOne
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformFlagSet
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerVisible
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.service.PlatformConsts
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * This processor creates tags when the keyboard starts and finishes appearing.
+ * @param logger logs by invoking any event messages
+ */
+class ImeAppearProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.IME_APPEAR
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitInputMethodVisible(tags)
+
+ /**
+ * FSM state that waits until the InputMethod is visible in both WM and SF.
+ */
+ inner class WaitInputMethodVisible(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ private val newImeVisible = isImeShown(PlatformConsts.DEFAULT_DISPLAY)
+ private val prevImeInvisible = newImeVisible.negate()
+
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+
+ return if (newImeVisible.isSatisfied(current) &&
+ prevImeInvisible.isSatisfied(previous)) {
+ processInputMethodVisible(current)
+ } else {
+ this
+ }
+ }
+
+ private fun processInputMethodVisible(
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ logger.invoke("(${current.layerState.timestamp}) IME appear started.")
+ // add factory method as well
+ val inputMethodLayer = current.layerState.visibleLayers.first {
+ it.name.contains(FlickerComponentName.IME.toLayerName())
+ }
+ addStartTransitionTag(current, transition, layerId = inputMethodLayer.id)
+ return WaitImeAppearFinished(tags, inputMethodLayer.id)
+ }
+ }
+
+ /**
+ * FSM state to check when the Ime Appear has finished by opaque color alpha of input method
+ * and it has finished transforming and scaling.
+ */
+ inner class WaitImeAppearFinished(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val layerId: Int
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val isImeAppearFinished = isImeAppearFinished.isSatisfied(current)
+
+ return if (isImeAppearFinished) {
+ // tag on the last complete state at the start
+ logger.invoke("(${current.layerState.timestamp}) Ime appear end detected.")
+ addEndTransitionTag(current, transition, layerId = layerId)
+ // return to start to wait for a second IME appear
+ WaitInputMethodVisible(tags)
+ } else {
+ logger.invoke("(${current.layerState.timestamp}) Ime appear hasn't finished.")
+ this
+ }
+ }
+
+ private val isImeAppearFinished = ConditionList(listOf(
+ isLayerVisible(FlickerComponentName.IME),
+ isLayerColorAlphaOne(FlickerComponentName.IME),
+ isLayerTransformFlagSet(FlickerComponentName.IME, Transform.TRANSLATE_VAL),
+ isLayerTransformFlagSet(FlickerComponentName.IME, Transform.SCALE_VAL).negate()
+ ))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeDisappearProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeDisappearProcessor.kt
new file mode 100644
index 000000000..3aff56f3c
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/ImeDisappearProcessor.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.ConditionList
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isImeShown
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerColorAlphaOne
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformFlagSet
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.service.PlatformConsts
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * This processor creates tags when the keyboard starts and finishes disappearing.
+ * @param logger logs by invoking any event messages
+ */
+class ImeDisappearProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.IME_DISAPPEAR
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitImeDisappearStart(tags)
+
+ /**
+ * FSM state that waits until the IME begins to disappear
+ * Different conditions required for IME closing by gesture (layer color alpha < 1), compared
+ * to IME closing via app close (layer translate SCALE_VAL bit set)
+ */
+ inner class WaitImeDisappearStart(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ private val isImeShown = isImeShown(PlatformConsts.DEFAULT_DISPLAY)
+ private val isImeAppeared =
+ ConditionList(listOf(
+ isImeShown,
+ isLayerColorAlphaOne(FlickerComponentName.IME),
+ isLayerTransformFlagSet(FlickerComponentName.IME, Transform.TRANSLATE_VAL),
+ isLayerTransformFlagSet(FlickerComponentName.IME, Transform.SCALE_VAL).negate()
+ ))
+ private val isImeDisappearByGesture =
+ ConditionList(listOf(
+ isImeShown,
+ isLayerColorAlphaOne(FlickerComponentName.IME).negate()
+ ))
+ private val isImeDisappearByAppClose =
+ ConditionList(listOf(
+ isImeShown,
+ isLayerTransformFlagSet(FlickerComponentName.IME, Transform.SCALE_VAL)
+ ))
+
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+
+ return if (isImeAppeared.isSatisfied(previous) &&
+ (isImeDisappearByGesture.isSatisfied(current) ||
+ isImeDisappearByAppClose.isSatisfied(current))) {
+ processImeDisappearing(current)
+ } else {
+ logger.invoke("(${current.layerState.timestamp}) IME disappear not started.")
+ this
+ }
+ }
+
+ private fun processImeDisappearing(
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ logger.invoke("(${current.layerState.timestamp}) IME disappear started.")
+ val inputMethodLayer = current.layerState.visibleLayers.first {
+ it.name.contains(FlickerComponentName.IME.toLayerName())
+ }
+ addStartTransitionTag(current, transition, layerId = inputMethodLayer.id)
+ return WaitImeDisappearFinished(tags, inputMethodLayer.id)
+ }
+ }
+
+ /**
+ * FSM state to check when the IME disappear has finished i.e. when the input method layer is
+ * no longer visible.
+ */
+ inner class WaitImeDisappearFinished(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val layerId: Int
+ ) : BaseState(tags) {
+ private val imeNotShown = isImeShown(PlatformConsts.DEFAULT_DISPLAY).negate()
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ return if (imeNotShown.isSatisfied(current)) {
+ // tag on the last complete state at the start
+ logger.invoke("(${current.layerState.timestamp}) IME disappear end detected.")
+ addEndTransitionTag(current, transition, layerId = layerId)
+ // return to start to wait for a second IME disappear
+ WaitImeDisappearStart(tags)
+ } else {
+ logger.invoke("(${current.layerState.timestamp}) IME disappear not finished.")
+ this
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipEnterProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipEnterProcessor.kt
new file mode 100644
index 000000000..4ec409952
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipEnterProcessor.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.ConditionList
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.hasPipWindow
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isAppTransitionIdle
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isPipWindowLayerSizeMatch
+import com.android.server.wm.traces.common.layers.Layer
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.service.PlatformConsts
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * Processor to detect PIP mode enter.
+ * Waits for a window to enter pip mode, then tags transition start at the last moment before
+ * corresponding layer started scaling.
+ * Tags transition end when window and layer stop animating and their sizes match.
+ */
+class PipEnterProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.PIP_ENTER
+ private val allScalingLayers = mutableMapOf<Int, Long>()
+
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitPipEnterStart(tags)
+
+ /**
+ * FSM state that waits for a pinned window to appear. Until this occurs, it watches all
+ * scaling layers, recording when they started to scale in the companion object. When the
+ * pinned window has appeared, it adds a start tag at the timestamp at which the layer with
+ * the same id as the pinned window layerId started to scale.
+ */
+ inner class WaitPipEnterStart(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+
+ private fun getScalingLayers(
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): List<Layer> = current.layerState.flattenedLayers.filter {
+ WindowManagerConditionsFactory.isLayerTransformFlagSet(
+ it.id,
+ Transform.SCALE_VAL
+ ).isSatisfied(current)
+ }
+
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+
+ // get current scaling layers and add to allScalingLayers map for whole trace
+ val scalingLayers = getScalingLayers(current)
+ val scalingLayerIds = scalingLayers.map { it.id }
+ scalingLayerIds.forEach {
+ allScalingLayers.getOrPut(it) { previous.layerState.timestamp }
+ }
+
+ // remove all layers that are no longer scaling from allScalingLayers map
+ val notScalingLayerIds = allScalingLayers.keys.filter { !scalingLayerIds.contains(it) }
+ notScalingLayerIds.forEach {
+ allScalingLayers.remove(it)
+ }
+
+ return if (hasPipWindow().isSatisfied(current) &&
+ hasPipWindow().negate().isSatisfied(previous)) {
+ processPipEnterStart(current)
+ } else {
+ logger.invoke("(${current.layerState.timestamp}) PIP enter not started.")
+ this
+ }
+ }
+
+ private fun processPipEnterStart(
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val pipWindow = current.wmState.pinnedWindows.first()
+ val startTimestamp = allScalingLayers[pipWindow.layerId]
+
+ if (startTimestamp != null) {
+ addStartTransitionTag(
+ current,
+ transition,
+ layerId = pipWindow.layerId,
+ timestamp = startTimestamp
+ )
+ } else {
+ addStartTransitionTag(
+ current,
+ transition,
+ layerId = pipWindow.layerId
+ )
+ }
+ // reset all scaling layers
+ allScalingLayers.clear()
+
+ logger.invoke("($startTimestamp) PIP enter started.")
+ return WaitPipEnterFinished(tags, pipWindow.layerId)
+ }
+ }
+
+ /**
+ * FSM state to check when the PIP enter has finished. This is when the pinned window
+ * has the same size as the associated layer.
+ */
+ inner class WaitPipEnterFinished(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val layerId: Int
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ return if (isPipEnterFinished.isSatisfied(current)) {
+ // tag on the last complete state at the start
+ logger.invoke("(${current.wmState.timestamp}) PIP enter finished.")
+ addEndTransitionTag(current, transition, layerId = layerId)
+
+ // return to start to wait for a second PIP enter
+ WaitPipEnterStart(tags)
+ } else {
+ logger.invoke("(${current.wmState.timestamp}) PIP enter not finished.")
+ this
+ }
+ }
+
+ private val isPipEnterFinished = ConditionList(listOf(
+ isAppTransitionIdle(PlatformConsts.DEFAULT_DISPLAY),
+ hasPipWindow(),
+ isPipWindowLayerSizeMatch(layerId)
+ ))
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExitProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExitProcessor.kt
new file mode 100644
index 000000000..3ed713e8d
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExitProcessor.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.hasLayersAnimating
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isAppTransitionIdle
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerColorAlphaOne
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformFlagSet
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerVisible
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isWMStateComplete
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * This processor creates tags when the pip window is exited directly to home screen or a
+ * different app altogether.
+ * @param logger logs by invoking any event messages
+ */
+class PipExitProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.PIP_EXIT
+ private val scalingWindows =
+ HashMap<String, DeviceStateDump<WindowManagerState, LayerTraceEntry>>()
+ private val areLayersAnimating = hasLayersAnimating()
+ private val wmStateIdle = isAppTransitionIdle(/* default display */ 0)
+ private val wmStateComplete = isWMStateComplete()
+
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitPinnedWindowSwipedOrFading(tags)
+
+ /**
+ * Initial FSM state which waits until the app window in pip mode starts to change opacity.
+ */
+ inner class WaitPinnedWindowSwipedOrFading(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+ // There can only ever be one pinned window at a time
+ val nextPinnedWindow = next.wmState.pinnedWindows.firstOrNull()
+ val currPinnedWindow = current.wmState.pinnedWindows.firstOrNull() ?: return this
+
+ if (nextPinnedWindow == null) {
+ val dump = scalingWindows[currPinnedWindow.token]
+ if (dump != null) {
+ // Pip app unpinned so let's tag.
+ addStartTransitionTag(dump, transition,
+ layerId = currPinnedWindow.layerId,
+ windowToken = currPinnedWindow.token
+ )
+ addEndTransitionTag(previous, transition,
+ layerId = currPinnedWindow.layerId,
+ windowToken = currPinnedWindow.token
+ )
+ }
+ return this
+ }
+
+ // close pip by swiping
+ val isScaling = isLayerTransformFlagSet(currPinnedWindow.layerId, Transform.SCALE_VAL)
+ val movingPinnedWindow =
+ isScaling.isSatisfied(current) && isScaling.negate().isSatisfied(previous)
+
+ // close pip by pressing dismiss button
+ val colorAlphaIsOne = isLayerColorAlphaOne(currPinnedWindow.layerId)
+ val pinnedWindowFading = colorAlphaIsOne.negate().isSatisfied(current) &&
+ colorAlphaIsOne.isSatisfied(previous) &&
+ isScaling.negate().isSatisfied(current)
+
+ return when {
+ movingPinnedWindow -> {
+ // Record last time when pip app started scaling
+ scalingWindows[currPinnedWindow.token] = current
+ this
+ }
+ pinnedWindowFading -> {
+ addStartTransitionTag(previous, transition,
+ layerId = currPinnedWindow.layerId,
+ windowToken = currPinnedWindow.token
+ )
+ WaitUntilPipColorAlphaIsOneAndInvisible(tags, currPinnedWindow.layerId)
+ }
+ else -> {
+ this
+ }
+ }
+ }
+ }
+
+ inner class WaitUntilPipColorAlphaIsOneAndInvisible(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val layerId: Int
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val layerInvisible = isLayerVisible(layerId).negate().isSatisfied(current)
+ val layerColorAlphaOne = isLayerColorAlphaOne(layerId).isSatisfied(current)
+ val isStableState = wmStateIdle.isSatisfied(current) ||
+ wmStateComplete.isSatisfied(current) ||
+ areLayersAnimating.negate().isSatisfied(current)
+
+ return if (layerInvisible && layerColorAlphaOne && isStableState) {
+ addEndTransitionTag(current, transition, layerId = layerId)
+ WaitPinnedWindowSwipedOrFading(tags)
+ } else {
+ this
+ }
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExpandProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExpandProcessor.kt
new file mode 100644
index 000000000..c15b731df
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipExpandProcessor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformFlagSet
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformIdentity
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * This processor creates tags when an pip window starts to expand from window to full screen app.
+ * @param logger logs by invoking any event messages
+ */
+class PipExpandProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.PIP_EXPAND
+ private val scalingLayers =
+ HashMap<Int, DeviceStateDump<WindowManagerState, LayerTraceEntry>>()
+
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitUntilAppIsNoLongerPinned(tags)
+
+ /**
+ * We wait until the pip app is no longer pinned and is ready to expand.
+ */
+ inner class WaitUntilAppIsNoLongerPinned(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ // Pinned window is no longer pinned
+ val prevPinnedWindows = previous?.wmState?.pinnedWindows?.toList() ?: emptyList()
+ val currPinnedWindows = current.wmState.pinnedWindows.toList()
+
+ if (prevPinnedWindows.isNotEmpty() && currPinnedWindows.isEmpty()) {
+ return WaitUntilAppCompletesExpanding(tags, prevPinnedWindows.first().layerId)
+ }
+ return this
+ }
+ }
+
+ /**
+ * FSMState when app has been unpinned and we track its corresponding layer.
+ * We record every time the layer is scaling and check it has transform identity
+ * with increased bounds.
+ */
+ inner class WaitUntilAppCompletesExpanding(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val layerId: Int
+ ) : BaseState(tags) {
+ private val isScaling = isLayerTransformFlagSet(layerId, Transform.SCALE_VAL)
+ private val isIdentity = isLayerTransformIdentity(layerId)
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ if (previous == null) return this
+ if (previous.wmState.pinnedWindows.isNotEmpty()) {
+ return WaitUntilAppIsNoLongerPinned(tags)
+ }
+
+ val startedScaling = isScaling.isSatisfied(current) &&
+ isScaling.negate().isSatisfied(previous)
+
+ val currLayerBounds = current.layerState.getLayerById(layerId)?.bounds ?: return this
+ val prevLayerBounds = previous.layerState.getLayerById(layerId)?.bounds ?: return this
+ val finishedExpanding = isScaling.isSatisfied(previous) &&
+ isIdentity.isSatisfied(current) &&
+ currLayerBounds.height > prevLayerBounds.height &&
+ currLayerBounds.width > prevLayerBounds.width
+
+ if (startedScaling) {
+ scalingLayers[layerId] = current
+ } else if (finishedExpanding) {
+ val dump = scalingLayers[layerId]
+ if (dump != null) {
+ addStartTransitionTag(current, transition,
+ layerId = layerId,
+ timestamp = dump.layerState.timestamp
+ )
+ addEndTransitionTag(current, transition,
+ layerId = layerId,
+ timestamp = current.layerState.timestamp
+ )
+ scalingLayers.clear()
+ return WaitUntilAppIsNoLongerPinned(tags)
+ }
+ }
+ return this
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipResizeProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipResizeProcessor.kt
new file mode 100644
index 000000000..7bb89d23b
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/PipResizeProcessor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isLayerTransformFlagSet
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * This processor creates tags when the pip window is resized but is still in pip mode.
+ * @param logger logs by invoking any event messages
+ */
+class PipResizeProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.PIP_RESIZE
+ private val scalingWindows =
+ HashMap<String, DeviceStateDump<WindowManagerState, LayerTraceEntry>>()
+
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) =
+ WaitUntilAppStopsAnimatingYetStillPinned(tags)
+
+ inner class WaitUntilAppStopsAnimatingYetStillPinned(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val currPinnedWindow = current.wmState.pinnedWindows.firstOrNull() ?: return this
+ previous?.wmState?.pinnedWindows?.firstOrNull() ?: return this
+
+ val isScaling = isLayerTransformFlagSet(currPinnedWindow.layerId, Transform.SCALE_VAL)
+ val startedScaling = isScaling.negate().isSatisfied(previous) &&
+ isScaling.isSatisfied(current)
+ if (startedScaling) {
+ // remember when pinned window/layer starting scaling
+ scalingWindows[currPinnedWindow.token] = previous
+ }
+
+ // Bounds have changed and layer no longer scaling
+ val currBounds = current.layerState.getLayerById(currPinnedWindow.layerId)?.bounds
+ val prevBounds = previous.layerState.getLayerById(currPinnedWindow.layerId)?.bounds
+ val finishedResizing = isScaling.isSatisfied(previous) &&
+ isScaling.negate().isSatisfied(current) &&
+ (currBounds?.height != prevBounds?.height) &&
+ (currBounds?.width != prevBounds?.width)
+
+ if (finishedResizing) {
+ val lastScaledDump = scalingWindows[currPinnedWindow.token]
+ if (lastScaledDump != null) {
+ addStartTransitionTag(lastScaledDump, transition,
+ layerId = currPinnedWindow.layerId,
+ windowToken = currPinnedWindow.token,
+ timestamp = lastScaledDump.layerState.timestamp
+ )
+ addEndTransitionTag(lastScaledDump, transition,
+ layerId = currPinnedWindow.layerId,
+ windowToken = currPinnedWindow.token,
+ timestamp = current.layerState.timestamp
+ )
+ scalingWindows.remove(currPinnedWindow.token)
+ }
+ }
+ return this
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/RotationProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/RotationProcessor.kt
new file mode 100644
index 000000000..be29cedef
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/RotationProcessor.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.RectF
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+
+/**
+ * Processor to detect rotations.
+ *
+ * First check the WM state for a rotation change, then wait the SF rotation
+ * to occur and both nav and status bars to appear
+ */
+class RotationProcessor(logger: (String) -> Unit) : TransitionProcessor(logger) {
+ override val transition = Transition.ROTATION
+ override fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>) = InitialState(tags)
+
+ /**
+ * Initial FSM state, obtains the current display size and start searching
+ * for display size changes
+ */
+ inner class InitialState(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val currDisplayRect = current.wmState.displaySize()
+ logger.invoke("(${current.wmState.timestamp}) Initial state. " +
+ "Display size $currDisplayRect")
+ return WaitDisplayRectChange(tags, currDisplayRect)
+ }
+ }
+
+ /**
+ * FSM state when the display size has not changed since [InitialState]
+ */
+ inner class WaitDisplayRectChange(
+ tags: MutableMap<Long, MutableList<Tag>>,
+ private val currDisplayRect: RectF
+ ) : BaseState(tags) {
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val newWmDisplayRect = current.wmState.displaySize()
+ val newLayersDisplayRect = current.layerState.screenBounds()
+
+ return when {
+ // WM display changed first (Regular rotation)
+ // SF display changed first (Seamless rotation)
+ newWmDisplayRect != currDisplayRect || newLayersDisplayRect != currDisplayRect -> {
+ requireNotNull(previous) { "Should have a previous state" }
+ val rect = if (newWmDisplayRect != currDisplayRect) {
+ newWmDisplayRect
+ } else {
+ newLayersDisplayRect
+ }
+ processDisplaySizeChange(previous, rect)
+ }
+ else -> {
+ logger.invoke("(${current.wmState.timestamp}) No display size change")
+ this
+ }
+ }
+ }
+
+ private fun processDisplaySizeChange(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ newDisplayRect: RectF
+ ): FSMState {
+ logger.invoke("(${previous.wmState.timestamp}) Display size changed " +
+ "to $newDisplayRect")
+ // tag on the last complete state at the start
+ logger.invoke("(${previous.wmState.timestamp}) Tagging transition start")
+ addStartTransitionTag(previous, transition)
+ return WaitRotationFinished(tags)
+ }
+ }
+
+ /**
+ * FSM state for when the animation occurs in the SF trace
+ */
+ inner class WaitRotationFinished(tags: MutableMap<Long, MutableList<Tag>>) : BaseState(tags) {
+ private val rotationLayerExists = WindowManagerConditionsFactory
+ .isLayerVisible(FlickerComponentName.ROTATION)
+ private val backSurfaceLayerExists = WindowManagerConditionsFactory
+ .isLayerVisible(FlickerComponentName.BACK_SURFACE)
+ private val areLayersAnimating = WindowManagerConditionsFactory.hasLayersAnimating()
+ private val wmStateIdle = WindowManagerConditionsFactory
+ .isAppTransitionIdle(/* default display */ 0)
+ private val wmStateComplete = WindowManagerConditionsFactory.isWMStateComplete()
+
+ override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState {
+ val anyLayerAnimating = areLayersAnimating.isSatisfied(current)
+ val rotationLayerExists = rotationLayerExists.isSatisfied(current)
+ val blackSurfaceLayerExists = backSurfaceLayerExists.isSatisfied(current)
+ val wmStateIdle = wmStateIdle.isSatisfied(current)
+ val wmStateComplete = wmStateComplete.isSatisfied(current)
+
+ val newWmDisplayRect = current.wmState.displaySize()
+ val newLayersDisplayRect = current.layerState.screenBounds()
+ val displaySizeDifferent = newWmDisplayRect != newLayersDisplayRect
+
+ val inRotation = anyLayerAnimating || rotationLayerExists || blackSurfaceLayerExists ||
+ displaySizeDifferent || !wmStateIdle || !wmStateComplete
+ logger.invoke("(${current.layerState.timestamp}) " +
+ "In rotation? $inRotation (" +
+ "anyLayerAnimating=$anyLayerAnimating, " +
+ "blackSurfaceLayerExists=$blackSurfaceLayerExists, " +
+ "rotationLayerExists=$rotationLayerExists, " +
+ "wmStateIdle=$wmStateIdle, " +
+ "wmStateComplete=$wmStateComplete, " +
+ "displaySizeDifferent=$displaySizeDifferent)")
+ return if (inRotation) {
+ this
+ } else {
+ // tag on the last complete state at the start
+ logger.invoke("(${current.layerState.timestamp}) Tagging transition end")
+ addEndTransitionTag(current, transition)
+ // return to start to wait for a second rotation
+ val lastDisplayRect = current.wmState.displaySize()
+ WaitDisplayRectChange(tags, lastDisplayRect)
+ }
+ }
+ }
+
+ companion object {
+ private fun LayerTraceEntry.screenBounds() = this.displays.minByOrNull { it.id }
+ ?.layerStackSpace?.toRectF() ?: this.children
+ .sortedBy { it.id }
+ .firstOrNull { it.isRootLayer }
+ ?.screenBounds ?: error("Unable to identify screen bounds (display is empty in proto)")
+
+ private fun WindowManagerState.displaySize() = getDefaultDisplay()
+ ?.displayRect?.toRectF() ?: RectF.EMPTY
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/TransitionProcessor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/TransitionProcessor.kt
new file mode 100644
index 000000000..31b30ba97
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/service/processors/TransitionProcessor.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.service.processors
+
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.service.ITagProcessor
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.TagState
+import com.android.server.wm.traces.common.tags.TagTrace
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+
+/**
+ * This class implements the relevant methods such as generating tags, creating dumps for the
+ * WindowManager and SurfaceFlinger traces, and ensuring the 1:1 correspondence between the start
+ * and end tags invariant is maintained by [BaseFsmState].
+ */
+abstract class TransitionProcessor(internal val logger: (String) -> Unit) : ITagProcessor {
+ abstract val transition: Transition
+ abstract fun getInitialState(tags: MutableMap<Long, MutableList<Tag>>): BaseState
+
+ abstract inner class BaseState(
+ tags: MutableMap<Long, MutableList<Tag>>
+ ) : BaseFsmState(tags, logger, transition) {
+ abstract override fun doProcessState(
+ previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?,
+ current: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
+ next: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ): FSMState
+ }
+
+ /**
+ * Add the start and end tags corresponding to the transition from
+ * the WindowManager and SurfaceFlinger traces
+ * @param wmTrace - WindowManager trace
+ * @param layersTrace - SurfaceFlinger trace
+ * @return [TagTrace] - containing all the newly generated tags in states with
+ * timestamps
+ */
+ override fun generateTags(
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace
+ ): TagTrace {
+ val tags = mutableMapOf<Long, MutableList<Tag>>()
+ var currPosition: FSMState? = getInitialState(tags)
+
+ val dumpList = createDumpList(wmTrace, layersTrace)
+ val dumpIterator = dumpList.iterator()
+
+ // keep always a reference to previous, current and next states
+ var previous: DeviceStateDump<WindowManagerState, LayerTraceEntry>?
+ var current: DeviceStateDump<WindowManagerState, LayerTraceEntry>? = null
+ var next: DeviceStateDump<WindowManagerState, LayerTraceEntry>? = dumpIterator.next()
+ while (currPosition != null) {
+ previous = current
+ current = next
+ next = if (dumpIterator.hasNext()) dumpIterator.next() else null
+ requireNotNull(current) { "Current state shouldn't be null" }
+ val newPosition = currPosition.process(previous, current, next)
+ currPosition = newPosition
+ }
+
+ return buildTagTrace(tags)
+ }
+
+ private fun buildTagTrace(tags: MutableMap<Long, MutableList<Tag>>): TagTrace {
+ val tagStates = tags.map { entry ->
+ val timestamp = entry.key
+ val stateTags = entry.value
+ TagState(timestamp.toString(), stateTags.toTypedArray())
+ }
+ return TagTrace(tagStates.toTypedArray(), source = "")
+ }
+
+ companion object {
+ internal fun createDumpList(
+ wmTrace: WindowManagerTrace,
+ layersTrace: LayersTrace
+ ): List<DeviceStateDump<WindowManagerState, LayerTraceEntry>> {
+ val wmTimestamps = wmTrace.map { it.timestamp }.toTypedArray()
+ val layersTimestamps = layersTrace.map { it.timestamp }.toTypedArray()
+ val fullTimestamps = setOf(*wmTimestamps, *layersTimestamps).sorted()
+
+ return fullTimestamps.map { baseTimestamp ->
+ val wmState = wmTrace
+ .lastOrNull { it.timestamp <= baseTimestamp }
+ ?: wmTrace.first()
+ val layerState = layersTrace
+ .lastOrNull { it.timestamp <= baseTimestamp }
+ ?: layersTrace.first()
+ DeviceStateDump(wmState, layerState)
+ }.distinctBy { Pair(it.wmState.timestamp, it.layerState.timestamp) }
+ }
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/tags/Tag.kt b/libraries/flicker/src/com/android/server/wm/traces/common/tags/Tag.kt
new file mode 100644
index 000000000..c5ecc7da1
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/tags/Tag.kt
@@ -0,0 +1,28 @@
+package com.android.server.wm.traces.common.tags
+
+/**
+ * Tag Class relating to a particular transition event in a WindowManager
+ * or SurfaceFlinger trace state.
+ * @param id The id to match the end and start tags
+ * @param transition Transition the tag represents the transition
+ * @param isStartTag Tag represents the start or end moment in transition
+ * @param layerId The Layer the tag is associated with (or 0 if no taskId associated with it)
+ * @param windowToken The Window the tag is associated
+ * with (or empty string if no taskId associated with it)
+ * @param taskId The Task the tag is associated with (or 0 if no taskId associated with it)
+ */
+data class Tag(
+ val id: Int,
+ val transition: Transition,
+ val isStartTag: Boolean,
+ val layerId: Int = 0,
+ val windowToken: String = "",
+ val taskId: Int = 0
+) {
+ override fun toString(): String {
+ if (isStartTag) {
+ return "Start Of $transition"
+ }
+ return "End Of $transition"
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/tags/TagState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/tags/TagState.kt
new file mode 100644
index 000000000..adfc163c6
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/tags/TagState.kt
@@ -0,0 +1,37 @@
+package com.android.server.wm.traces.common.tags
+
+import com.android.server.wm.traces.common.ITraceEntry
+import com.android.server.wm.traces.common.prettyTimestamp
+
+/**
+ * Holds the list of tags corresponding to a particular state at a particular time in trace.
+ *
+ * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
+ *
+ * @param _timestamp Timestamp of the state
+ * @param tags Array of tags contained in the state
+ * @param isFallback False indicate if the tag timestamp was found or true if a default tag is made
+ */
+class TagState(
+ _timestamp: String,
+ val tags: Array<Tag>,
+ val isFallback: Boolean = false
+) : ITraceEntry {
+ override val timestamp: Long = _timestamp.toLong()
+ override fun toString(): String = "FlickerTagState(timestamp=${prettyTimestamp(timestamp)}, " +
+ "tags=$tags)"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is TagState) return false
+ if (timestamp != other.timestamp) return false
+ if (!tags.contentEquals(other.tags)) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = timestamp.hashCode()
+ result = 31 * result + tags.contentDeepHashCode()
+ return result
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/tags/TagTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/tags/TagTrace.kt
new file mode 100644
index 000000000..9aa578fac
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/tags/TagTrace.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.tags
+
+import com.android.server.wm.traces.common.ITrace
+
+/**
+ * Holds the entire list of [TagState]s representing an entire trace that has been tagged.
+ * @param entries Array of tagged states within the trace
+ * @param source Source of the trace file
+ */
+data class TagTrace(
+ override val entries: Array<TagState>,
+ override val source: String
+) : ITrace<TagState>,
+ List<TagState> by entries.toList() {
+ override fun toString(): String = "FlickerTagTrace(${entries.firstOrNull()?.timestamp ?: 0}, " +
+ "${entries.lastOrNull()?.timestamp ?: 0})"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is TagTrace) return false
+ if (!entries.contentEquals(other.entries)) return false
+ if (source != other.source) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entries.contentDeepHashCode()
+ result = 31 * result + source.hashCode()
+ return result
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/tags/Transition.kt b/libraries/flicker/src/com/android/server/wm/traces/common/tags/Transition.kt
new file mode 100644
index 000000000..204d3e253
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/tags/Transition.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.tags
+
+/**
+ * Represents all the possible transitions to be tagged.
+ */
+enum class Transition(private val transitionName: String) {
+ ROTATION("Rotation"),
+ APP_LAUNCH("AppLaunching"),
+ APP_CLOSE("AppClosing"),
+ PIP_ENTER("PipEntering"),
+ PIP_RESIZE("PipResizing"),
+ PIP_EXPAND("PipExpanding"),
+ PIP_EXIT("PipExiting"),
+ IME_APPEAR("ImeAppearing"),
+ IME_DISAPPEAR("ImeDisappearing");
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/tags/TransitionTag.kt b/libraries/flicker/src/com/android/server/wm/traces/common/tags/TransitionTag.kt
new file mode 100644
index 000000000..d2be4af41
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/tags/TransitionTag.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.tags
+
+/**
+ * Saves the information about a transition tag.
+ */
+data class TransitionTag(
+ var tag: Tag,
+ var startTimestamp: Long,
+ var endTimestamp: Long
+) {
+ fun isEmpty(): Boolean {
+ return this.tag.layerId == 0 &&
+ this.tag.taskId == 0 &&
+ this.tag.windowToken.isEmpty()
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt
index 34b7598d9..ccb31f134 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt
@@ -16,15 +16,13 @@
package com.android.server.wm.traces.common.windowmanager
-import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.ITraceEntry
import com.android.server.wm.traces.common.prettyTimestamp
import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayArea
import com.android.server.wm.traces.common.windowmanager.windows.DisplayContent
import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.ActivityTask
+import com.android.server.wm.traces.common.windowmanager.windows.Task
import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
import com.android.server.wm.traces.common.windowmanager.windows.WindowState
@@ -35,6 +33,8 @@ import com.android.server.wm.traces.common.windowmanager.windows.WindowState
* This is a generic object that is reused by both Flicker and Winscope and cannot
* access internal Java/Android functionality
*
+ * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
+ *
**/
open class WindowManagerState(
val where: String,
@@ -48,8 +48,9 @@ open class WindowManagerState(
val pendingActivities: Array<String>,
val root: RootWindowContainer,
val keyguardControllerState: KeyguardControllerState,
- override val timestamp: Long = 0
+ _timestamp: String = "0"
) : ITraceEntry {
+ override val timestamp: Long = _timestamp.toLong()
val isVisible: Boolean = true
val stableId: String get() = this::class.simpleName ?: error("Unable to determine class")
val name: String get() = prettyTimestamp(timestamp)
@@ -65,7 +66,7 @@ open class WindowManagerState(
get() = windowContainers.filterIsInstance<DisplayContent>().toTypedArray()
// Stacks in z-order with the top most at the front of the list, starting with primary display.
- val rootTasks: Array<ActivityTask>
+ val rootTasks: Array<Task>
get() = displays.flatMap { it.rootTasks.toList() }.toTypedArray()
// Windows in z-order with the top most at the front of the list.
@@ -86,9 +87,17 @@ open class WindowManagerState(
get() = windowStates
.dropWhile { !appWindows.contains(it) }.drop(appWindows.size).toTypedArray()
val visibleWindows: Array<WindowState>
- get() = windowStates.filter { it.isSurfaceShown }.toTypedArray()
+ get() = windowStates
+ .filter { it.isVisible }
+ .filter { window ->
+ val activities = getActivitiesForWindow(window.title)
+ val activity = activities.firstOrNull { it.children.contains(window) }
+ activity?.isVisible ?: true
+ }
+ .toTypedArray()
val topVisibleAppWindow: String
- get() = appWindows.filter { it.isVisible }
+ get() = visibleWindows
+ .filter { it.isAppWindow }
.map { it.title }
.firstOrNull() ?: ""
val pinnedWindows: Array<WindowState>
@@ -96,6 +105,12 @@ open class WindowManagerState(
.filter { it.windowingMode == WINDOWING_MODE_PINNED }
.toTypedArray()
+ /**
+ * Checks if the device state supports rotation, i.e., if the rotation sensor is
+ * enabled (e.g., launcher) and if the rotation not fixed
+ */
+ val canRotate: Boolean
+ get() = policy?.isFixedOrientation != true && policy?.isOrientationNoSensor != true
val focusedDisplay: DisplayContent? get() = getDisplay(focusedDisplayId)
val focusedStackId: Int get() = focusedDisplay?.focusedRootTaskId ?: -1
val focusedActivity: String get() {
@@ -103,46 +118,19 @@ open class WindowManagerState(
return if (focusedDisplay != null && focusedDisplay.resumedActivity.isNotEmpty()) {
focusedDisplay.resumedActivity
} else {
- getActivityForWindow(focusedWindow, focusedDisplayId)?.name ?: ""
+ getActivitiesForWindow(focusedWindow, focusedDisplayId).firstOrNull()?.name ?: ""
}
}
- val resumedActivitiesInDisplays: Array<String>
- get() = displays.flatMap { display ->
- display.rootTasks.flatMap { it.resumedActivities.toList() }
- }.toTypedArray()
- val defaultPinnedStackBounds: Rect
- get() = displays
- .lastOrNull { it.defaultPinnedStackBounds.isNotEmpty }?.defaultPinnedStackBounds
- ?: Rect.EMPTY
- val pinnedStackMovementBounds: Rect
- get() = displays
- .lastOrNull { it.defaultPinnedStackBounds.isNotEmpty }?.pinnedStackMovementBounds
- ?: Rect.EMPTY
- val focusedStackActivityType: Int
- get() = getRootTask(focusedStackId)?.activityType ?: ACTIVITY_TYPE_UNDEFINED
- val focusedStackWindowingMode: Int
- get() = getRootTask(focusedStackId)?.windowingMode ?: WINDOWING_MODE_UNDEFINED
val resumedActivities: Array<String>
get() = rootTasks.flatMap { it.resumedActivities.toList() }.toTypedArray()
val resumedActivitiesCount: Int get() = resumedActivities.size
val stackCount: Int get() = rootTasks.size
- val displayCount: Int get() = displays.size
- val homeTask: ActivityTask? get() = getStackByActivityType(ACTIVITY_TYPE_HOME)?.topTask
- val recentsTask: ActivityTask? get() = getStackByActivityType(ACTIVITY_TYPE_RECENTS)?.topTask
+ val homeTask: Task? get() = getStackByActivityType(ACTIVITY_TYPE_HOME)?.topTask
+ val recentsTask: Task? get() = getStackByActivityType(ACTIVITY_TYPE_RECENTS)?.topTask
val homeActivity: Activity? get() = homeTask?.activities?.lastOrNull()
val recentsActivity: Activity? get() = recentsTask?.activities?.lastOrNull()
- val rootTasksCount: Int get() = rootTasks.size
val isRecentsActivityVisible: Boolean get() = recentsActivity?.isVisible ?: false
- val dreamTask: ActivityTask?
- get() = getStackByActivityType(ACTIVITY_TYPE_DREAM)?.topTask
- val defaultDisplayLastTransition: String get() = getDefaultDisplay()?.lastTransition
- ?: "Default display not found"
- val defaultDisplayAppTransitionState: String get() = getDefaultDisplay()?.appTransitionState
- ?: "Default display not found"
- val allNavigationBarStates: Array<WindowState>
- get() = windowStates.filter { it.isValidNavBarType }.toTypedArray()
val frontWindow: String? get() = windowStates.map { it.title }.firstOrNull()
- val stableBounds: Rect get() = getDefaultDisplay()?.stableBounds ?: Rect.EMPTY
val inputMethodWindowState: WindowState?
get() = getWindowStateForAppToken(inputMethodWindowAppToken)
@@ -152,56 +140,6 @@ open class WindowManagerState(
fun getDisplay(displayId: Int): DisplayContent? =
displays.firstOrNull { it.id == displayId }
- fun getTaskDisplayArea(activityName: String): DisplayArea? {
- val result = displays.mapNotNull { it.getTaskDisplayArea(activityName) }
-
- if (result.size > 1) {
- throw IllegalArgumentException(
- "There must be exactly one activity among all TaskDisplayAreas.")
- }
-
- return result.firstOrNull()
- }
-
- fun getFrontRootTaskId(displayId: Int): Int =
- getDisplay(displayId)?.rootTasks?.first()?.rootTaskId ?: 0
-
- fun getFrontStackActivityType(displayId: Int): Int =
- getDisplay(displayId)?.rootTasks?.first()?.activityType ?: 0
-
- fun getFrontStackWindowingMode(displayId: Int): Int =
- getDisplay(displayId)?.rootTasks?.first()?.windowingMode ?: 0
-
- fun getTopActivityName(displayId: Int): String {
- return getDisplay(displayId)
- ?.rootTasks?.firstOrNull()
- ?.topTask
- ?.activities?.firstOrNull()
- ?.title
- ?: ""
- }
-
- fun getResumedActivitiesCountInPackage(packageName: String): Int {
- val componentPrefix = "$packageName/"
- var count = 0
- displays.forEach { display ->
- display.rootTasks.forEach { task ->
- count += task.resumedActivities.count {
- it.isNotEmpty() && it.startsWith(componentPrefix)
- }
- }
- }
- return count
- }
-
- fun getResumedActivity(displayId: Int): String {
- return getDisplay(displayId)?.resumedActivity ?: ""
- }
-
- fun containsStack(windowingMode: Int, activityType: Int): Boolean {
- return countStacks(windowingMode, activityType) > 0
- }
-
fun countStacks(windowingMode: Int, activityType: Int): Int {
var count = 0
for (stack in rootTasks) {
@@ -216,7 +154,7 @@ open class WindowManagerState(
return count
}
- fun getRootTask(taskId: Int): ActivityTask? =
+ fun getRootTask(taskId: Int): Task? =
rootTasks.firstOrNull { it.rootTaskId == taskId }
fun getRotation(displayId: Int): Int =
@@ -225,223 +163,53 @@ open class WindowManagerState(
fun getOrientation(displayId: Int): Int =
getDisplay(displayId)?.lastOrientation ?: error("Default display not found")
- fun getStackByActivityType(activityType: Int): ActivityTask? =
+ fun getStackByActivityType(activityType: Int): Task? =
rootTasks.firstOrNull { it.activityType == activityType }
- fun getStandardStackByWindowingMode(windowingMode: Int): ActivityTask? =
+ fun getStandardStackByWindowingMode(windowingMode: Int): Task? =
rootTasks.firstOrNull {
it.activityType == ACTIVITY_TYPE_STANDARD &&
it.windowingMode == windowingMode
}
- fun getStandardTaskCountByWindowingMode(windowingMode: Int): Int {
- var count = 0
- for (stack in rootTasks) {
- if (stack.activityType != ACTIVITY_TYPE_STANDARD) {
- continue
- }
- if (stack.windowingMode == windowingMode) {
- count += if (stack.tasks.isEmpty()) 1 else stack.tasks.size
- }
- }
- return count
- }
-
- /** Get the stack on its display. */
- fun getStackByActivity(activityName: String): ActivityTask? {
- return displays.map { display ->
- display.rootTasks.reversed().firstOrNull { stack ->
- stack.containsActivity(activityName)
- }
- }.firstOrNull()
- }
-
/**
- * Get the first activity on display with id [displayId], containing a window whose title
+ * Get the all activities on display with id [displayId], containing a window whose title
* contains [partialWindowTitle]
*
* @param partialWindowTitle window title to search
* @param displayId display where to search the activity
*/
- fun getActivityForWindow(
+ fun getActivitiesForWindow(
partialWindowTitle: String,
displayId: Int = DEFAULT_DISPLAY
- ): Activity? {
- return displays.firstOrNull { it.id == displayId }?.rootTasks?.map { stack ->
+ ): List<Activity> {
+ return displays.firstOrNull { it.id == displayId }?.rootTasks?.mapNotNull { stack ->
stack.getActivity { activity ->
activity.hasWindow(partialWindowTitle)
}
- }?.firstOrNull()
- }
-
- /** Get the stack position on its display. */
- fun getStackIndexByActivityType(activityType: Int): Int {
- return displays
- .map { it.rootTasks.indexOfFirst { p -> p.activityType == activityType } }
- .firstOrNull { it > -1 }
- ?: -1
- }
-
- /** Get the stack position on its display. */
- fun getStackIndexByActivity(activityName: String): Int {
- for (display in displays) {
- for (i in display.rootTasks.indices.reversed()) {
- val stack = display.rootTasks[i]
- if (stack.containsActivity(activityName)) return i
- }
- }
- return -1
- }
-
- /** Get display id by activity on it. */
- fun getDisplayByActivity(activityComponent: String): Int {
- val task = getTaskByActivity(activityComponent) ?: return -1
- return getRootTask(task.rootTaskId)?.displayId
- ?: error("Task with name $activityComponent not found")
+ } ?: emptyList()
}
fun containsActivity(activityName: String): Boolean =
rootTasks.any { it.containsActivity(activityName) }
- fun containsNoneOf(activityNames: Iterable<String>): Boolean {
- for (activityName in activityNames) {
- for (stack in rootTasks) {
- if (stack.containsActivity(activityName)) return false
- }
- }
- return true
- }
-
- fun containsActivityInWindowingMode(
- activityName: String,
- windowingMode: Int
- ): Boolean {
- for (stack in rootTasks) {
- val activity = stack.getActivity(activityName)
- if (activity != null && activity.windowingMode == windowingMode) {
- return true
- }
- }
- return false
+ fun isActivityVisible(activityName: String): Boolean {
+ val activity = rootTasks.mapNotNull { it.getActivity(activityName) }.firstOrNull()
+ return activity?.isVisible ?: false
}
- fun isActivityVisible(activityName: String): Boolean =
- rootTasks.map { it.getActivity(activityName)?.isVisible ?: false }.firstOrNull()
- ?: false
-
- fun isActivityTranslucent(activityName: String): Boolean =
- rootTasks.map { it.getActivity(activityName)?.isTranslucent ?: false }.firstOrNull()
- ?: false
-
- fun isBehindOpaqueActivities(activityName: String): Boolean {
- for (stack in rootTasks) {
- val activity = stack.getActivity { a -> a.title == activityName || !a.isTranslucent }
- if (activity != null) {
- if (activity.title == activityName) {
- return false
- }
- if (!activity.isTranslucent) {
- return true
- }
- }
- }
-
- return false
- }
-
- fun containsStartedActivities(): Boolean = rootTasks.map {
- it.getActivity { a -> a.state != STATE_STOPPED && a.state != STATE_DESTROYED } != null
- }.firstOrNull() ?: false
-
fun hasActivityState(activityName: String, activityState: String): Boolean =
rootTasks.any { it.getActivity(activityName)?.state == activityState }
- fun getActivityProcId(activityName: String): Int =
- rootTasks.mapNotNull { it.getActivity(activityName)?.procId }
- .firstOrNull()
- ?: -1
-
- fun getStackIdByActivity(activityName: String): Int =
- getTaskByActivity(activityName)?.rootTaskId ?: INVALID_STACK_ID
-
- fun getTaskByActivity(activityName: String): ActivityTask? =
- getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED)
-
- fun getTaskByActivity(activityName: String, windowingMode: Int): ActivityTask? {
- for (stack in rootTasks) {
- if (windowingMode == WINDOWING_MODE_UNDEFINED || windowingMode == stack.windowingMode) {
- val task = stack.getTask { it.getActivity(activityName) != null }
- if (task != null) {
- return task
- }
- }
- }
- return null
- }
-
- /**
- * Get the number of activities in the task, with the option to count only activities with
- * specific name.
- * @param taskId Id of the task where we're looking for the number of activities.
- * @param activityName Optional name of the activity we're interested in.
- * @return Number of all activities in the task if activityName is `null`, otherwise will
- * report number of activities that have specified name.
- */
- fun getActivityCountInTask(taskId: Int, activityName: String?): Int {
- // If activityName is null, count all activities in the task.
- // Otherwise count activities that have specified name.
- for (stack in rootTasks) {
- val task = stack.getTask(taskId) ?: continue
-
- if (activityName == null) {
- return task.activities.size
- }
- var count = 0
- for (activity in task.activities) {
- if (activity.title == activityName) {
- count++
- }
- }
- return count
- }
- return 0
- }
-
- fun getRootTasksCount(displayId: Int): Int {
- var count = 0
- for (rootTask in rootTasks) {
- if (rootTask.displayId == displayId) ++count
- }
- return count
- }
-
fun pendingActivityContain(activityName: String): Boolean {
return pendingActivities.contains(activityName)
}
- fun getMatchingVisibleWindowState(windowName: String): List<WindowState> {
- return windowStates.filter { it.isSurfaceShown && it.title == windowName }
+ fun getMatchingVisibleWindowState(windowName: String): Array<WindowState> {
+ return windowStates.filter { it.isSurfaceShown && it.title.contains(windowName) }
+ .toTypedArray()
}
- fun getWindowByPackageName(packageName: String, windowType: Int): WindowState? =
- getWindowsByPackageName(packageName, windowType).firstOrNull()
-
- fun getWindowsByPackageName(
- packageName: String,
- vararg restrictToTypes: Int
- ): List<WindowState> =
- windowStates.filter { ws ->
- ((ws.title == packageName ||
- ws.title.startsWith("$packageName/")) &&
- restrictToTypes.any { type -> type == ws.attributes.type })
- }
-
- fun getMatchingWindowType(type: Int): List<WindowState> =
- windowStates.filter { it.attributes.type == type }
-
- fun getMatchingWindowTokens(windowName: String): List<String> =
- windowStates.filter { it.title === windowName }.map { it.token }
-
fun getNavBarWindow(displayId: Int): WindowState? {
val navWindow = windowStates.filter { it.isValidNavBarType && it.displayId == displayId }
@@ -460,21 +228,13 @@ open class WindowManagerState(
* Check if there exists a window record with matching windowName.
*/
fun containsWindow(windowName: String): Boolean =
- windowStates.any { it.title == windowName }
+ windowStates.any { it.title.contains(windowName) }
/**
* Check if at least one window which matches the specified name has shown it's surface.
*/
- fun isWindowSurfaceShown(windowName: String): Boolean {
- for (window in windowStates) {
- if (window.title == windowName) {
- if (window.isSurfaceShown) {
- return true
- }
- }
- }
- return false
- }
+ fun isWindowSurfaceShown(windowName: String): Boolean =
+ getMatchingVisibleWindowState(windowName).isNotEmpty()
/**
* Check if at least one window which matches provided window name is visible.
@@ -494,35 +254,8 @@ open class WindowManagerState(
return pinnedWindows.any { it.title.contains(windowName) }
}
- /**
- * Checks whether the display contains the given activity.
- */
- fun hasActivityInDisplay(displayId: Int, activityName: String): Boolean {
- for (stack in getDisplay(displayId)!!.rootTasks) {
- if (stack.containsActivity(activityName)) {
- return true
- }
- }
- return false
- }
-
- fun findFirstWindowWithType(type: Int): WindowState? =
- windowStates.firstOrNull { it.attributes.type == type }
-
fun getZOrder(w: WindowState): Int = windowStates.size - windowStates.indexOf(w)
- fun getStandardRootStackByWindowingMode(windowingMode: Int): ActivityTask? {
- for (task in rootTasks) {
- if (task.activityType != ACTIVITY_TYPE_STANDARD) {
- continue
- }
- if (task.windowingMode == windowingMode) {
- return task
- }
- }
- return null
- }
-
fun defaultMinimalTaskSize(displayId: Int): Int =
dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP.toFloat(), getDisplay(displayId)!!.dpi)
@@ -568,8 +301,10 @@ open class WindowManagerState(
!keyguardControllerState.isKeyguardShowing
}
+ fun asTrace(): WindowManagerTrace = WindowManagerTrace(arrayOf(this), source = "")
+
override fun toString(): String {
- return prettyTimestamp(timestamp)
+ return "${prettyTimestamp(timestamp)} (timestamp=$timestamp)"
}
companion object {
@@ -583,10 +318,8 @@ open class WindowManagerState(
internal const val ACTIVITY_TYPE_STANDARD = 1
internal const val DEFAULT_DISPLAY = 0
internal const val DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440
- internal const val INVALID_STACK_ID = -1
internal const val ACTIVITY_TYPE_HOME = 2
internal const val ACTIVITY_TYPE_RECENTS = 3
- internal const val ACTIVITY_TYPE_DREAM = 5
internal const val WINDOWING_MODE_UNDEFINED = 0
private const val DENSITY_DEFAULT = 160
/**
@@ -595,7 +328,7 @@ open class WindowManagerState(
private const val WINDOWING_MODE_PINNED = 2
/**
- * @see WindowManager.LayoutParams
+ * @see android.view.WindowManager.LayoutParams
*/
internal const val TYPE_NAVIGATION_BAR_PANEL = 2024
@@ -608,4 +341,7 @@ open class WindowManagerState(
return (dp * densityDpi / DENSITY_DEFAULT + 0.5f).toInt()
}
}
+ override fun equals(other: Any?): Boolean {
+ return other is WindowManagerState && other.timestamp == this.timestamp
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt
index cd937ac52..3517ba92d 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt
@@ -28,14 +28,45 @@ import com.android.server.wm.traces.common.ITrace
* access internal Java/Android functionality
*
*/
-open class WindowManagerTrace(
- override val entries: List<WindowManagerState>,
- override val source: String,
- override val sourceChecksum: String
+data class WindowManagerTrace(
+ override val entries: Array<WindowManagerState>,
+ override val source: String
) : ITrace<WindowManagerState>,
- List<WindowManagerState> by entries {
+ List<WindowManagerState> by entries.toList() {
override fun toString(): String {
return "WindowManagerTrace(Start: ${entries.first()}, " +
"End: ${entries.last()})"
}
-} \ No newline at end of file
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WindowManagerTrace) return false
+
+ if (!entries.contentEquals(other.entries)) return false
+ if (source != other.source) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entries.contentHashCode()
+ result = 31 * result + source.hashCode()
+ return result
+ }
+
+ /**
+ * Split the trace by the start and end timestamp.
+ *
+ * @param from the start timestamp
+ * @param to the end timestamp
+ * @return the subtrace trace(from, to)
+ */
+ fun filter(from: Long, to: Long): WindowManagerTrace {
+ return WindowManagerTrace(
+ this.entries
+ .dropWhile { it.timestamp < from }
+ .dropLastWhile { it.timestamp > to }
+ .toTypedArray(),
+ source = "")
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt
index cff1a7f28..5c082a1a6 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt
@@ -38,10 +38,35 @@ open class Activity(
* @param partialWindowTitle window title to search
*/
fun hasWindow(partialWindowTitle: String): Boolean {
- return this.windows.any { it.title.contains(partialWindowTitle) }
+ return collectDescendants<WindowState> { it.title.contains(partialWindowTitle) }
+ .isNotEmpty()
}
override fun toString(): String {
return "${this::class.simpleName}: {$token $title} state=$state visible=$isVisible"
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Activity) return false
+
+ if (state != other.state) return false
+ if (frontOfTask != other.frontOfTask) return false
+ if (procId != other.procId) return false
+ if (isTranslucent != other.isTranslucent) return false
+ if (orientation != other.orientation) return false
+ if (title != other.title) return false
+ if (token != other.token) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + state.hashCode()
+ result = 31 * result + frontOfTask.hashCode()
+ result = 31 * result + procId
+ result = 31 * result + isTranslucent.hashCode()
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt
index 44c64f355..13d1f9e8f 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt
@@ -42,4 +42,32 @@ data class Configuration(
smallestScreenWidthDp == 0 &&
screenLayout == 0 &&
uiMode == 0
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Configuration) return false
+
+ if (windowConfiguration != other.windowConfiguration) return false
+ if (densityDpi != other.densityDpi) return false
+ if (orientation != other.orientation) return false
+ if (screenHeightDp != other.screenHeightDp) return false
+ if (screenWidthDp != other.screenWidthDp) return false
+ if (smallestScreenWidthDp != other.smallestScreenWidthDp) return false
+ if (screenLayout != other.screenLayout) return false
+ if (uiMode != other.uiMode) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = windowConfiguration?.hashCode() ?: 0
+ result = 31 * result + densityDpi
+ result = 31 * result + orientation
+ result = 31 * result + screenHeightDp
+ result = 31 * result + screenWidthDp
+ result = 31 * result + smallestScreenWidthDp
+ result = 31 * result + screenLayout
+ result = 31 * result + uiMode
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt
index 4ba0b6a08..6aacdb89d 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt
@@ -42,4 +42,22 @@ open class ConfigurationContainer(
get() = (overrideConfiguration?.isEmpty ?: true) &&
(fullConfiguration?.isEmpty ?: true) &&
(mergedOverrideConfiguration?.isEmpty ?: true)
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ConfigurationContainer) return false
+
+ if (overrideConfiguration != other.overrideConfiguration) return false
+ if (fullConfiguration != other.fullConfiguration) return false
+ if (mergedOverrideConfiguration != other.mergedOverrideConfiguration) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = overrideConfiguration?.hashCode() ?: 0
+ result = 31 * result + (fullConfiguration?.hashCode() ?: 0)
+ result = 31 * result + (mergedOverrideConfiguration?.hashCode() ?: 0)
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt
index 3067ac601..9c42887e3 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt
@@ -45,4 +45,23 @@ open class DisplayArea(
override fun toString(): String {
return "${this::class.simpleName} {$token $title} isTaskArea=$isTaskDisplayArea"
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DisplayArea) return false
+
+ if (isTaskDisplayArea != other.isTaskDisplayArea) return false
+ if (isVisible != other.isVisible) return false
+ if (orientation != other.orientation) return false
+ if (title != other.title) return false
+ if (token != other.token) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + isTaskDisplayArea.hashCode()
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt
index cb2167238..6671aee8f 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt
@@ -30,13 +30,13 @@ open class DisplayContent(
val focusedRootTaskId: Int,
val resumedActivity: String,
val singleTaskInstance: Boolean,
- _defaultPinnedStackBounds: Rect?,
- _pinnedStackMovementBounds: Rect?,
+ val defaultPinnedStackBounds: Rect,
+ val pinnedStackMovementBounds: Rect,
val displayRect: Rect,
val appRect: Rect,
val dpi: Int,
val flags: Int,
- _stableBounds: Rect?,
+ val stableBounds: Rect,
val surfaceSize: Int,
val focusedApp: String,
val lastTransition: String,
@@ -48,16 +48,12 @@ open class DisplayContent(
override val name: String = id.toString()
override val isVisible: Boolean = false
- val defaultPinnedStackBounds: Rect = _defaultPinnedStackBounds ?: Rect.EMPTY
- val pinnedStackMovementBounds: Rect = _pinnedStackMovementBounds ?: Rect.EMPTY
- val stableBounds: Rect = _stableBounds ?: Rect.EMPTY
-
- val rootTasks: Array<ActivityTask>
+ val rootTasks: Array<Task>
get() {
- val tasks = this.collectDescendants<ActivityTask> { it.isRootTask }.toMutableList()
+ val tasks = this.collectDescendants<Task> { it.isRootTask }.toMutableList()
// TODO(b/149338177): figure out how CTS tests deal with organizer. For now,
// don't treat them as regular stacks
- val rootOrganizedTasks = mutableListOf<ActivityTask>()
+ val rootOrganizedTasks = mutableListOf<Task>()
val reversedTaskList = tasks.reversed()
reversedTaskList.forEach { task ->
// Skip tasks created by an organizer
@@ -68,7 +64,7 @@ open class DisplayContent(
}
// Add root tasks controlled by an organizer
rootOrganizedTasks.reversed().forEach { task ->
- tasks.addAll(task.children.reversed().map { it as ActivityTask })
+ tasks.addAll(task.children.reversed().map { it as Task })
}
return tasks.toTypedArray()
@@ -93,4 +89,55 @@ open class DisplayContent(
return "${this::class.simpleName} #$id: name=$title mDisplayRect=$displayRect " +
"mAppRect=$appRect mFlags=$flags"
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DisplayContent) return false
+ if (!super.equals(other)) return false
+
+ if (id != other.id) return false
+ if (focusedRootTaskId != other.focusedRootTaskId) return false
+ if (resumedActivity != other.resumedActivity) return false
+ if (defaultPinnedStackBounds != other.defaultPinnedStackBounds) return false
+ if (pinnedStackMovementBounds != other.pinnedStackMovementBounds) return false
+ if (stableBounds != other.stableBounds) return false
+ if (displayRect != other.displayRect) return false
+ if (appRect != other.appRect) return false
+ if (dpi != other.dpi) return false
+ if (flags != other.flags) return false
+ if (focusedApp != other.focusedApp) return false
+ if (lastTransition != other.lastTransition) return false
+ if (appTransitionState != other.appTransitionState) return false
+ if (rotation != other.rotation) return false
+ if (lastOrientation != other.lastOrientation) return false
+ if (name != other.name) return false
+ if (singleTaskInstance != other.singleTaskInstance) return false
+ if (surfaceSize != other.surfaceSize) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + id
+ result = 31 * result + focusedRootTaskId
+ result = 31 * result + resumedActivity.hashCode()
+ result = 31 * result + singleTaskInstance.hashCode()
+ result = 31 * result + defaultPinnedStackBounds.hashCode()
+ result = 31 * result + pinnedStackMovementBounds.hashCode()
+ result = 31 * result + displayRect.hashCode()
+ result = 31 * result + appRect.hashCode()
+ result = 31 * result + dpi
+ result = 31 * result + flags
+ result = 31 * result + stableBounds.hashCode()
+ result = 31 * result + surfaceSize
+ result = 31 * result + focusedApp.hashCode()
+ result = 31 * result + lastTransition.hashCode()
+ result = 31 * result + appTransitionState.hashCode()
+ result = 31 * result + rotation
+ result = 31 * result + lastOrientation
+ result = 31 * result + name.hashCode()
+ result = 31 * result + isVisible.hashCode()
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ActivityTask.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Task.kt
index 63f80a347..1c17405a3 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ActivityTask.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Task.kt
@@ -25,14 +25,14 @@ import com.android.server.wm.traces.common.Rect
* access internal Java/Android functionality
*
*/
-open class ActivityTask(
+open class Task(
override val activityType: Int,
override val isFullscreen: Boolean,
override val bounds: Rect,
val taskId: Int,
val rootTaskId: Int,
val displayId: Int,
- _lastNonFullscreenBounds: Rect?,
+ val lastNonFullscreenBounds: Rect,
val realActivity: String,
val origActivity: String,
val resizeMode: Int,
@@ -48,18 +48,20 @@ open class ActivityTask(
override val isVisible: Boolean = false
override val name: String = taskId.toString()
override val isEmpty: Boolean get() = tasks.isEmpty() && activities.isEmpty()
+ override val stableId: String get() = "${super.stableId} $taskId"
- val lastNonFullscreenBounds: Rect = _lastNonFullscreenBounds ?: Rect.EMPTY
val isRootTask: Boolean get() = taskId == rootTaskId
- val tasks: List<ActivityTask>
- get() = this.children.reversed().filterIsInstance<ActivityTask>()
- val activities: List<Activity>
- get() = this.children.reversed().filterIsInstance<Activity>()
+ val tasks: Array<Task>
+ get() = this.children.reversed().filterIsInstance<Task>().toTypedArray()
+ val taskFragments: Array<TaskFragment>
+ get() = this.children.reversed().filterIsInstance<TaskFragment>().toTypedArray()
+ val activities: Array<Activity>
+ get() = this.children.reversed().filterIsInstance<Activity>().toTypedArray()
/** The top task in the stack.
*/
// NOTE: Unlike the WindowManager internals, we dump the state from top to bottom,
// so the indices are inverted
- val topTask: ActivityTask? get() = tasks.firstOrNull()
+ val topTask: Task? get() = tasks.firstOrNull()
val resumedActivities: Array<String> get() {
val result = mutableSetOf<String>()
if (this._resumedActivity.isNotEmpty()) {
@@ -72,23 +74,29 @@ open class ActivityTask(
return result.toTypedArray()
}
- fun getTask(predicate: (ActivityTask) -> Boolean) =
+ fun getTask(predicate: (Task) -> Boolean) =
tasks.firstOrNull { predicate(it) } ?: if (predicate(this)) this else null
fun getTask(taskId: Int) = getTask { t -> t.taskId == taskId }
- fun forAllTasks(consumer: (ActivityTask) -> Any) {
+ fun getActivityWithTask(predicate: (Task, Activity) -> Boolean): Activity? {
+ return activities.firstOrNull { predicate(this, it) }
+ ?: tasks.flatMap { task -> task.activities.filter { predicate(task, it) } }
+ .firstOrNull()
+ }
+
+ fun forAllTasks(consumer: (Task) -> Any) {
tasks.forEach { consumer(it) }
}
fun getActivity(predicate: (Activity) -> Boolean): Activity? {
return activities.firstOrNull { predicate(it) }
- ?: tasks.flatMap { it.activities }
+ ?: tasks.flatMap { it.activities.toList() }
.firstOrNull { predicate(it) }
}
fun getActivity(activityName: String): Activity? {
- return getActivity { activity -> activity.title == activityName }
+ return getActivity { activity -> activity.title.contains(activityName) }
}
fun containsActivity(activityName: String) = getActivity(activityName) != null
@@ -96,4 +104,50 @@ open class ActivityTask(
override fun toString(): String {
return "${this::class.simpleName}: {$token $title} id=$taskId bounds=$bounds"
}
-} \ No newline at end of file
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Task) return false
+
+ if (activityType != other.activityType) return false
+ if (isFullscreen != other.isFullscreen) return false
+ if (bounds != other.bounds) return false
+ if (taskId != other.taskId) return false
+ if (rootTaskId != other.rootTaskId) return false
+ if (displayId != other.displayId) return false
+ if (realActivity != other.realActivity) return false
+ if (resizeMode != other.resizeMode) return false
+ if (minWidth != other.minWidth) return false
+ if (minHeight != other.minHeight) return false
+ if (name != other.name) return false
+ if (orientation != other.orientation) return false
+ if (title != other.title) return false
+ if (token != other.token) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + activityType
+ result = 31 * result + isFullscreen.hashCode()
+ result = 31 * result + bounds.hashCode()
+ result = 31 * result + taskId
+ result = 31 * result + rootTaskId
+ result = 31 * result + displayId
+ result = 31 * result + lastNonFullscreenBounds.hashCode()
+ result = 31 * result + realActivity.hashCode()
+ result = 31 * result + origActivity.hashCode()
+ result = 31 * result + resizeMode
+ result = 31 * result + _resumedActivity.hashCode()
+ result = 31 * result + animatingBounds.hashCode()
+ result = 31 * result + surfaceWidth
+ result = 31 * result + surfaceHeight
+ result = 31 * result + createdByOrganizer.hashCode()
+ result = 31 * result + minWidth
+ result = 31 * result + minHeight
+ result = 31 * result + isVisible.hashCode()
+ result = 31 * result + name.hashCode()
+ return result
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt
new file mode 100644
index 000000000..d18daa0f0
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.common.windowmanager.windows
+
+/**
+ * Represents a task fragment in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot
+ * access internal Java/Android functionality
+ *
+ */
+open class TaskFragment(
+ override val activityType: Int,
+ val displayId: Int,
+ val minWidth: Int,
+ val minHeight: Int,
+ windowContainer: WindowContainer
+) : WindowContainer(windowContainer) {
+ val tasks: Array<Task>
+ get() = this.children.reversed().filterIsInstance<Task>().toTypedArray()
+ val taskFragments: Array<TaskFragment>
+ get() = this.children.reversed().filterIsInstance<TaskFragment>().toTypedArray()
+ val activities: Array<Activity>
+ get() = this.children.reversed().filterIsInstance<Activity>().toTypedArray()
+
+ override fun toString(): String {
+ return "${this::class.simpleName}: {$token $title} bounds=$bounds"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is TaskFragment) return false
+
+ if (activityType != other.activityType) return false
+ if (displayId != other.displayId) return false
+ if (minWidth != other.minWidth) return false
+ if (minHeight != other.minHeight) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + activityType
+ result = 31 * result + displayId
+ result = 31 * result + minWidth
+ result = 31 * result + minHeight
+ return result
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt
index ffdda8298..c2eb894e4 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt
@@ -42,4 +42,26 @@ open class WindowConfiguration(
maxBounds.isEmpty &&
windowingMode == 0 &&
activityType == 0
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WindowConfiguration) return false
+
+ if (windowingMode != other.windowingMode) return false
+ if (activityType != other.activityType) return false
+ if (appBounds != other.appBounds) return false
+ if (bounds != other.bounds) return false
+ if (maxBounds != other.maxBounds) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = windowingMode
+ result = 31 * result + activityType
+ result = 31 * result + appBounds.hashCode()
+ result = 31 * result + bounds.hashCode()
+ result = 31 * result + maxBounds.hashCode()
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt
index db48e0dc2..b81f2abd4 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt
@@ -31,6 +31,7 @@ open class WindowContainer constructor(
val title: String,
val token: String,
val orientation: Int,
+ val layerId: Int,
_isVisible: Boolean,
configurationContainer: ConfigurationContainer,
val children: Array<WindowContainer>
@@ -43,6 +44,7 @@ open class WindowContainer constructor(
titleOverride ?: windowContainer.title,
windowContainer.token,
windowContainer.orientation,
+ windowContainer.layerId,
isVisibleOverride ?: windowContainer.isVisible,
windowContainer,
windowContainer.children
@@ -50,13 +52,10 @@ open class WindowContainer constructor(
open val isVisible: Boolean = _isVisible
open val name: String = title
- val stableId: String get() = "${this::class.simpleName} $token $title"
+ open val stableId: String get() = "${this::class.simpleName} $token $title"
open val isFullscreen: Boolean = false
open val bounds: Rect = Rect.EMPTY
- val windows: Array<WindowState>
- get() = this.collectDescendants()
-
fun traverseTopDown(): List<WindowContainer> {
val traverseList = mutableListOf(this)
@@ -111,6 +110,33 @@ open class WindowContainer constructor(
return name
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WindowContainer) return false
+
+ if (title != other.title) return false
+ if (token != other.token) return false
+ if (orientation != other.orientation) return false
+ if (isVisible != other.isVisible) return false
+ if (name != other.name) return false
+ if (isFullscreen != other.isFullscreen) return false
+ if (bounds != other.bounds) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = title.hashCode()
+ result = 31 * result + token.hashCode()
+ result = 31 * result + orientation
+ result = 31 * result + children.contentHashCode()
+ result = 31 * result + isVisible.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + isFullscreen.hashCode()
+ result = 31 * result + bounds.hashCode()
+ return result
+ }
+
override val isEmpty: Boolean
get() = super.isEmpty &&
title.isEmpty() &&
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt
index def86db27..9c73e6d92 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt
@@ -63,4 +63,78 @@ data class WindowLayoutParams(
*/
private const val TYPE_NAVIGATION_BAR = 2019
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WindowLayoutParams) return false
+
+ if (type != other.type) return false
+ if (x != other.x) return false
+ if (y != other.y) return false
+ if (width != other.width) return false
+ if (height != other.height) return false
+ if (horizontalMargin != other.horizontalMargin) return false
+ if (verticalMargin != other.verticalMargin) return false
+ if (gravity != other.gravity) return false
+ if (softInputMode != other.softInputMode) return false
+ if (format != other.format) return false
+ if (windowAnimations != other.windowAnimations) return false
+ if (alpha != other.alpha) return false
+ if (screenBrightness != other.screenBrightness) return false
+ if (buttonBrightness != other.buttonBrightness) return false
+ if (rotationAnimation != other.rotationAnimation) return false
+ if (preferredRefreshRate != other.preferredRefreshRate) return false
+ if (preferredDisplayModeId != other.preferredDisplayModeId) return false
+ if (hasSystemUiListeners != other.hasSystemUiListeners) return false
+ if (inputFeatureFlags != other.inputFeatureFlags) return false
+ if (userActivityTimeout != other.userActivityTimeout) return false
+ if (colorMode != other.colorMode) return false
+ if (flags != other.flags) return false
+ if (privateFlags != other.privateFlags) return false
+ if (systemUiVisibilityFlags != other.systemUiVisibilityFlags) return false
+ if (subtreeSystemUiVisibilityFlags != other.subtreeSystemUiVisibilityFlags) return false
+ if (appearance != other.appearance) return false
+ if (behavior != other.behavior) return false
+ if (fitInsetsTypes != other.fitInsetsTypes) return false
+ if (fitInsetsSides != other.fitInsetsSides) return false
+ if (fitIgnoreVisibility != other.fitIgnoreVisibility) return false
+ if (isValidNavBarType != other.isValidNavBarType) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = type
+ result = 31 * result + x
+ result = 31 * result + y
+ result = 31 * result + width
+ result = 31 * result + height
+ result = 31 * result + horizontalMargin.hashCode()
+ result = 31 * result + verticalMargin.hashCode()
+ result = 31 * result + gravity
+ result = 31 * result + softInputMode
+ result = 31 * result + format
+ result = 31 * result + windowAnimations
+ result = 31 * result + alpha.hashCode()
+ result = 31 * result + screenBrightness.hashCode()
+ result = 31 * result + buttonBrightness.hashCode()
+ result = 31 * result + rotationAnimation
+ result = 31 * result + preferredRefreshRate.hashCode()
+ result = 31 * result + preferredDisplayModeId
+ result = 31 * result + hasSystemUiListeners.hashCode()
+ result = 31 * result + inputFeatureFlags
+ result = 31 * result + userActivityTimeout.hashCode()
+ result = 31 * result + colorMode
+ result = 31 * result + flags
+ result = 31 * result + privateFlags
+ result = 31 * result + systemUiVisibilityFlags
+ result = 31 * result + subtreeSystemUiVisibilityFlags
+ result = 31 * result + appearance
+ result = 31 * result + behavior
+ result = 31 * result + fitInsetsTypes
+ result = 31 * result + fitInsetsSides
+ result = 31 * result + fitIgnoreVisibility.hashCode()
+ result = 31 * result + isValidNavBarType.hashCode()
+ return result
+ }
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt
index 2bc52d073..cc06fa522 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt
@@ -37,4 +37,78 @@ data class WindowManagerPolicy(
val rotationMode: Int,
val screenOnFully: Boolean,
val windowManagerDrawComplete: Boolean
-) \ No newline at end of file
+) {
+ val isOrientationNoSensor: Boolean
+ get() = orientation == SCREEN_ORIENTATION_NOSENSOR
+
+ val isFixedOrientation: Boolean
+ get() = isFixedOrientationLandscape ||
+ isFixedOrientationPortrait ||
+ orientation == SCREEN_ORIENTATION_LOCKED
+
+ private val isFixedOrientationLandscape
+ get() = orientation == SCREEN_ORIENTATION_LANDSCAPE ||
+ orientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE ||
+ orientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE ||
+ orientation == SCREEN_ORIENTATION_USER_LANDSCAPE
+
+ private val isFixedOrientationPortrait
+ get() = orientation == SCREEN_ORIENTATION_PORTRAIT ||
+ orientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT ||
+ orientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT ||
+ orientation == SCREEN_ORIENTATION_USER_PORTRAIT
+
+ companion object {
+ /**
+ * From [android.content.pm.ActivityInfo]
+ */
+ private const val SCREEN_ORIENTATION_LANDSCAPE = 0
+ private const val SCREEN_ORIENTATION_PORTRAIT = 1
+ private const val SCREEN_ORIENTATION_NOSENSOR = 5
+ private const val SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6
+ private const val SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7
+ private const val SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8
+ private const val SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9
+ private const val SCREEN_ORIENTATION_USER_LANDSCAPE = 11
+ private const val SCREEN_ORIENTATION_USER_PORTRAIT = 12
+ private const val SCREEN_ORIENTATION_LOCKED = 14
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WindowManagerPolicy) return false
+
+ if (focusedAppToken != other.focusedAppToken) return false
+ if (forceStatusBar != other.forceStatusBar) return false
+ if (forceStatusBarFromKeyguard != other.forceStatusBarFromKeyguard) return false
+ if (keyguardDrawComplete != other.keyguardDrawComplete) return false
+ if (keyguardOccluded != other.keyguardOccluded) return false
+ if (keyguardOccludedChanged != other.keyguardOccludedChanged) return false
+ if (keyguardOccludedPending != other.keyguardOccludedPending) return false
+ if (lastSystemUiFlags != other.lastSystemUiFlags) return false
+ if (orientation != other.orientation) return false
+ if (rotation != other.rotation) return false
+ if (rotationMode != other.rotationMode) return false
+ if (screenOnFully != other.screenOnFully) return false
+ if (windowManagerDrawComplete != other.windowManagerDrawComplete) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = focusedAppToken.hashCode()
+ result = 31 * result + forceStatusBar.hashCode()
+ result = 31 * result + forceStatusBarFromKeyguard.hashCode()
+ result = 31 * result + keyguardDrawComplete.hashCode()
+ result = 31 * result + keyguardOccluded.hashCode()
+ result = 31 * result + keyguardOccludedChanged.hashCode()
+ result = 31 * result + keyguardOccludedPending.hashCode()
+ result = 31 * result + lastSystemUiFlags
+ result = 31 * result + orientation
+ result = 31 * result + rotation
+ result = 31 * result + rotationMode
+ result = 31 * result + screenOnFully.hashCode()
+ result = 31 * result + windowManagerDrawComplete.hashCode()
+ return result
+ }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt
index d85e860d2..f11bfc8fa 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.traces.common.windowmanager.windows
-import com.android.server.wm.traces.common.Bounds
+import com.android.server.wm.traces.common.Size
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.Region
@@ -34,31 +34,22 @@ open class WindowState(
val layer: Int,
val isSurfaceShown: Boolean,
val windowType: Int,
- val requestedSize: Bounds,
+ val requestedSize: Size,
val surfacePosition: Rect?,
- _frame: Rect?,
- _containingFrame: Rect?,
- _parentFrame: Rect?,
- _contentFrame: Rect?,
- _contentInsets: Rect?,
- _surfaceInsets: Rect?,
- _givenContentInsets: Rect?,
- _crop: Rect?,
+ val frame: Rect,
+ val containingFrame: Rect,
+ val parentFrame: Rect,
+ val contentFrame: Rect,
+ val contentInsets: Rect,
+ val surfaceInsets: Rect,
+ val givenContentInsets: Rect,
+ val crop: Rect,
windowContainer: WindowContainer,
val isAppWindow: Boolean
) : WindowContainer(windowContainer, getWindowTitle(windowContainer.title)) {
override val isVisible: Boolean get() = super.isVisible && attributes.alpha > 0
- val frame: Rect = _frame ?: Rect.EMPTY
- val containingFrame: Rect = _containingFrame ?: Rect.EMPTY
- val parentFrame: Rect = _parentFrame ?: Rect.EMPTY
- val contentFrame: Rect = _contentFrame ?: Rect.EMPTY
- val contentInsets: Rect = _contentInsets ?: Rect.EMPTY
- val surfaceInsets: Rect = _surfaceInsets ?: Rect.EMPTY
- val givenContentInsets: Rect = _givenContentInsets ?: Rect.EMPTY
- val crop: Rect = _crop ?: Rect.EMPTY
override val isFullscreen: Boolean get() = this.attributes.flags.and(FLAG_FULLSCREEN) > 0
-
val isStartingWindow: Boolean = windowType == WINDOW_TYPE_STARTING
val isExitingWindow: Boolean = windowType == WINDOW_TYPE_EXITING
val isDebuggerWindow: Boolean = windowType == WINDOW_TYPE_DEBUGGER
@@ -79,13 +70,29 @@ open class WindowState(
"type=${attributes.type} cf=$containingFrame pf=$parentFrame"
override fun equals(other: Any?): Boolean {
- return other is WindowState &&
- other.stableId == stableId &&
- other.attributes == attributes &&
- other.token == token &&
- other.title == title &&
- other.containingFrame == containingFrame &&
- other.parentFrame == parentFrame
+ if (this === other) return true
+ if (other !is WindowState) return false
+
+ if (name != other.name) return false
+ if (attributes != other.attributes) return false
+ if (displayId != other.displayId) return false
+ if (stackId != other.stackId) return false
+ if (layer != other.layer) return false
+ if (isSurfaceShown != other.isSurfaceShown) return false
+ if (windowType != other.windowType) return false
+ if (requestedSize != other.requestedSize) return false
+ if (surfacePosition != other.surfacePosition) return false
+ if (frame != other.frame) return false
+ if (containingFrame != other.containingFrame) return false
+ if (parentFrame != other.parentFrame) return false
+ if (contentFrame != other.contentFrame) return false
+ if (contentInsets != other.contentInsets) return false
+ if (surfaceInsets != other.surfaceInsets) return false
+ if (givenContentInsets != other.givenContentInsets) return false
+ if (crop != other.crop) return false
+ if (isAppWindow != other.isAppWindow) return false
+
+ return true
}
override fun hashCode(): Int {
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt
index cfcaafc30..9d2b911aa 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt
@@ -28,4 +28,20 @@ open class WindowToken(windowContainer: WindowContainer) : WindowContainer(windo
override fun toString(): String {
return "${this::class.simpleName}: {$token $title}"
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WindowToken) return false
+ if (!super.equals(other)) return false
+
+ if (isVisible != other.isVisible) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + isVisible.hashCode()
+ return result
+ }
}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/Condition.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/Condition.kt
deleted file mode 100644
index be03b2d6a..000000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/Condition.kt
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.traces.parser
-
-import android.os.SystemClock
-import android.util.Log
-
-/**
- * The utility class to wait a condition with customized options.
- * The default retry policy is 5 times with interval 1 second.
- *
- * @param <T> The type of the object to validate.
- *
- * <p>Sample:</p>
- * <pre>
- * // Simple case.
- * if (Condition.waitFor("true value", () -> true)) {
- * println("Success");
- * }
- * // Wait for customized result with customized validation.
- * String result = Condition.waitForResult(new Condition<String>("string comparison")
- * .setResultSupplier(() -> "Result string")
- * .setResultValidator(str -> str.equals("Expected string"))
- * .setRetryIntervalMs(500)
- * .setRetryLimit(3)
- * .setOnFailure(str -> println("Failed on " + str)));
- * </pre>
- */
-class Condition<T>
-/**
- * Constructs with a simple boolean condition.
- *
- * When satisfier = null, it is expected that the condition will be configured with
- * [.setResultSupplier] and [.setResultValidator].
- *
- * @param message The message to show what is waiting for.
- * @param satisfier If it returns true, that means the condition is satisfied.
- */
-@JvmOverloads constructor(
- private var message: String = "",
- /**
- * It decides whether this condition is satisfied.
- */
- private var satisfier: (() -> Boolean)? = null,
- private var retryLimit: Int = DEFAULT_RETRY_LIMIT,
- private var retryIntervalMs: Long = DEFAULT_RETRY_INTERVAL_MS
-) {
- private var returnLastResult: Boolean = false
-
- /**
- * It is used when the condition is not a simple boolean expression, such as the caller may
- * want to get the validated product as the return value.
- */
- private var resultSupplier: (() -> T?)? = null
-
- /**
- * It validates the result from [.mResultSupplier].
- */
- private var resultValidator: ((T?) -> Boolean)? = null
- private var onFailure: ((T) -> Any)? = null
- private var onRetry: Runnable? = null
- private var lastResult: T? = null
- private var validatedResult: T? = null
-
- /**
- * Set the supplier which provides the result object to validate.
- */
- fun setResultSupplier(supplier: () -> T?): Condition<T> =
- apply { resultSupplier = supplier }
-
- /**
- * Set the validator which tests the object provided by the supplier.
- */
- fun setResultValidator(validator: (T?) -> Boolean): Condition<T> =
- apply { resultValidator = validator }
-
- /**
- * If true, when using [.waitForResult], the method will return the last result
- * provided by [.mResultSupplier] even it is not valid (by [.mResultValidator]).
- */
- fun setReturnLastResult(returnLastResult: Boolean): Condition<T> =
- apply { this.returnLastResult = returnLastResult }
-
- /**
- * Executes the action when the condition does not satisfy within the time limit. The passed
- * object to the consumer will be the last result from the supplier.
- */
- fun setOnFailure(onFailure: (T) -> Any): Condition<T> = apply { this.onFailure = onFailure }
-
- fun setOnRetry(onRetry: Runnable): Condition<T> = apply { this.onRetry = onRetry }
-
- fun setRetryIntervalMs(millis: Long): Condition<T> = apply { retryIntervalMs = millis }
-
- fun setRetryLimit(limit: Int): Condition<T> = apply { retryLimit = limit }
-
- /**
- * Build the condition by [.mResultSupplier] and [.mResultValidator].
- */
- private fun prepareSatisfier(): () -> Boolean {
- val supplier = resultSupplier
- val validator = resultValidator
- require(!(supplier == null || validator == null)) { "No specified condition" }
-
- return {
- val result = supplier.invoke()
- lastResult = result
- if (validator.invoke(result)) {
- validatedResult = result
- true
- } else {
- false
- }
- }.also {
- satisfier = it
- }
- }
-
- companion object {
- // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary
- // constant time, currently keep the default as 5*1s because most of the original code
- // uses it, and some tests might be sensitive to the waiting interval.
- private const val DEFAULT_RETRY_LIMIT = 5
- private const val DEFAULT_RETRY_INTERVAL_MS = 1000L
-
- /**
- * @see .waitFor
- * @see .Condition
- */
- @JvmStatic
- @JvmOverloads
- fun <T> waitFor(
- message: String,
- retryLimit: Int = DEFAULT_RETRY_LIMIT,
- retryIntervalMs: Long = DEFAULT_RETRY_INTERVAL_MS,
- satisfier: () -> Boolean
- ): Boolean {
- val condition = Condition<T>(message, satisfier, retryLimit, retryIntervalMs)
- return waitFor(condition)
- }
-
- /**
- * @return `false` if the condition does not satisfy within the time limit.
- */
- @JvmStatic
- fun <T> waitFor(condition: Condition<T>): Boolean {
- val satisfier = condition.satisfier ?: condition.prepareSatisfier()
- val startTime = SystemClock.elapsedRealtime()
- Log.v(LOG_TAG, "***Waiting for ${condition.message}")
- for (i in 1..condition.retryLimit) {
- if (satisfier.invoke()) {
- Log.v(LOG_TAG, "***Waiting for ${condition.message} ... Success!")
- return true
- } else {
- SystemClock.sleep(condition.retryIntervalMs)
- Log.v(LOG_TAG, "***Waiting for ${condition.message} ... retry=$i" +
- " elapsed=${SystemClock.elapsedRealtime() - startTime} ms")
- val onRetry = condition.onRetry
- if (onRetry != null && i < condition.retryLimit) {
- onRetry.run()
- }
- }
- }
- if (satisfier.invoke()) {
- Log.v(LOG_TAG, "***Waiting for ${condition.message} ... Success!")
- return true
- }
- val onFailure = condition.onFailure
- if (onFailure == null) {
- Log.e(LOG_TAG, "***Waiting for ${condition.message} ... Failed!")
- } else {
- val result = condition.lastResult
- require(result != null) { "Missing last result for failure notification" }
- onFailure.invoke(result)
- }
- return false
- }
-
- /**
- * @see .waitForResult
- */
- @JvmStatic
- fun <T> waitForResult(message: String, setup: (Condition<T>) -> Any): T? {
- val condition = Condition<T>(message)
- setup.invoke(condition)
- return waitForResult(condition)
- }
-
- /**
- * @return `null` if the condition does not satisfy within the time limit or the result
- * supplier returns `null`.
- */
- @JvmStatic
- fun <T> waitForResult(condition: Condition<T>): T? {
- condition.validatedResult = null
- condition.lastResult = condition.validatedResult
- condition.prepareSatisfier()
- waitFor(condition)
- return when {
- condition.validatedResult != null -> condition.validatedResult
- condition.returnLastResult -> condition.lastResult
- else -> null
- }
- }
- }
-} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceStateDump.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceDumpParser.kt
index 6534b3132..d47bdbc04 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceStateDump.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceDumpParser.kt
@@ -16,6 +16,8 @@
package com.android.server.wm.traces.parser
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.DeviceTraceDump
import com.android.server.wm.traces.common.layers.LayersTrace
import com.android.server.wm.traces.common.layers.LayerTraceEntry
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
@@ -27,16 +29,7 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParse
* Represents a state dump containing the [WindowManagerTrace] and the [LayersTrace] both parsed
* and in raw (byte) data.
*/
-class DeviceStateDump(
- /**
- * Parsed [WindowManagerTrace]
- */
- val wmTrace: WindowManagerTrace?,
- /**
- * Parsed [LayersTrace]
- */
- val layersTrace: LayersTrace?
-) {
+class DeviceDumpParser {
companion object {
/**
* Creates a device state dump containing the [WindowManagerTrace] and [LayersTrace]
@@ -47,15 +40,18 @@ class DeviceStateDump(
* @param layersTraceData [LayersTrace] content
*/
@JvmStatic
- fun fromDump(wmTraceData: ByteArray, layersTraceData: ByteArray): DeviceStateDump {
+ fun fromDump(
+ wmTraceData: ByteArray,
+ layersTraceData: ByteArray
+ ): DeviceStateDump<WindowManagerState?, LayerTraceEntry?> {
return DeviceStateDump(
- wmTrace = if (wmTraceData.isNotEmpty()) {
- WindowManagerTraceParser.parseFromDump(wmTraceData)
+ wmState = if (wmTraceData.isNotEmpty()) {
+ WindowManagerTraceParser.parseFromDump(wmTraceData).first()
} else {
null
},
- layersTrace = if (layersTraceData.isNotEmpty()) {
- LayersTraceParser.parseFromDump(layersTraceData)
+ layerState = if (layersTraceData.isNotEmpty()) {
+ LayersTraceParser.parseFromTrace(layersTraceData).first()
} else {
null
}
@@ -71,8 +67,8 @@ class DeviceStateDump(
* @param layersTraceData [LayersTrace] content
*/
@JvmStatic
- fun fromTrace(wmTraceData: ByteArray, layersTraceData: ByteArray): DeviceStateDump {
- return DeviceStateDump(
+ fun fromTrace(wmTraceData: ByteArray, layersTraceData: ByteArray): DeviceTraceDump {
+ return DeviceTraceDump(
wmTrace = if (wmTraceData.isNotEmpty()) {
WindowManagerTraceParser.parseFromTrace(wmTraceData)
} else {
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt
index bb20b5cc6..32f22252c 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt
@@ -22,8 +22,12 @@ import android.app.UiAutomation
import android.content.ComponentName
import android.os.ParcelFileDescriptor
import android.util.Log
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.Region
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
internal const val LOG_TAG = "AMWM_FLICKER"
@@ -35,9 +39,33 @@ fun Rect.toAndroidRect(): android.graphics.Rect {
return android.graphics.Rect(left, top, right, bottom)
}
-fun ComponentName.toActivityName(): String = this.flattenToShortString()
+/**
+ * Subtracts [other] region from this [this] region
+ */
+fun Region.minus(other: Region): android.graphics.Region = minus(other.toAndroidRegion())
+
+/**
+ * Subtracts [other] region from this [this] region
+ */
+fun Region.minus(other: android.graphics.Region): android.graphics.Region {
+ val thisRegion = this.toAndroidRegion()
+ thisRegion.op(other, android.graphics.Region.Op.XOR)
+ return thisRegion
+}
+
+/**
+ * Adds [other] region to this [this] region
+ */
+fun Region.plus(other: Region): android.graphics.Region = plus(other.toAndroidRegion())
-fun ComponentName.toWindowName(): String = this.flattenToString()
+/**
+ * Adds [other] region to this [this] region
+ */
+fun Region.plus(other: android.graphics.Region): android.graphics.Region {
+ val thisRegion = this.toAndroidRegion()
+ thisRegion.op(other, android.graphics.Region.Op.XOR)
+ return thisRegion
+}
private fun executeCommand(uiAutomation: UiAutomation, cmd: String): ByteArray {
val fileDescriptor = uiAutomation.executeShellCommand(cmd)
@@ -80,9 +108,15 @@ fun getCurrentState(
fun getCurrentStateDump(
uiAutomation: UiAutomation,
@WmStateDumpFlags dumpFlags: Int = FLAG_STATE_DUMP_FLAG_WM.or(FLAG_STATE_DUMP_FLAG_LAYERS)
-): DeviceStateDump {
+): DeviceStateDump<WindowManagerState?, LayerTraceEntry?> {
val currentStateDump = getCurrentState(uiAutomation, dumpFlags)
val wmTraceData = currentStateDump.first
val layersTraceData = currentStateDump.second
- return DeviceStateDump.fromDump(wmTraceData, layersTraceData)
+ return DeviceDumpParser.fromDump(wmTraceData, layersTraceData)
}
+
+/**
+ * Converts an Android [ComponentName] into a flicker [FlickerComponentName]
+ */
+fun ComponentName.toFlickerComponent(): FlickerComponentName =
+ FlickerComponentName(this.packageName, this.className)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/errors/ErrorTraceParserUtil.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/errors/ErrorTraceParserUtil.kt
new file mode 100644
index 000000000..e91b956eb
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/errors/ErrorTraceParserUtil.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.parser.errors
+
+import android.util.Log
+import com.android.server.wm.flicker.FlickerErrorTraceProto
+import com.android.server.wm.traces.common.errors.Error
+import com.android.server.wm.traces.common.errors.ErrorState
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import com.android.server.wm.traces.parser.LOG_TAG
+import java.nio.file.Path
+import kotlin.system.measureTimeMillis
+
+/**
+ * Class that holds the methods to parse error proto files into error classes.
+ */
+class ErrorTraceParserUtil {
+ companion object {
+ /**
+ * Parses [FlickerErrorTraceProto] from [data] and uses the proto to generates a list
+ * of trace entries, storing the flattened layers into its hierarchical structure.
+ *
+ * @param data binary proto data
+ * @param source Path to source of data for additional debug information
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun parseFromTrace(
+ data: ByteArray,
+ source: Path? = null
+ ): ErrorTrace {
+ var fileProto: FlickerErrorTraceProto? = null
+ try {
+ measureTimeMillis {
+ fileProto = FlickerErrorTraceProto.parseFrom(data)
+ }.also {
+ Log.v(LOG_TAG, "Parsing proto (Flicker Errors Trace): ${it}ms")
+ }
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+ return fileProto?.let {
+ parseFromTrace(it, source)
+ } ?: error("Unable to read flicker errors trace file")
+ }
+
+ /**
+ * Parses [FlickerErrorTraceProto] from [proto] and uses the proto to generates a list
+ * of trace entries, storing the flattened layers into its hierarchical structure.
+ *
+ * @param proto Parsed proto data
+ * @param source Path to source of data for additional debug information
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun parseFromTrace(
+ proto: FlickerErrorTraceProto,
+ source: Path? = null
+ ): ErrorTrace {
+ val states = mutableListOf<ErrorState>()
+ var traceParseTime = 0L
+ for (stateProto in proto.statesList) {
+ val errorParseTime = measureTimeMillis {
+ val errors = mutableListOf<Error>()
+ for (errorProto in stateProto.errorsList) {
+ errors.add(
+ Error(
+ stacktrace = errorProto.stacktrace,
+ message = errorProto.message,
+ layerId = errorProto.layerId,
+ windowToken = errorProto.windowToken,
+ taskId = errorProto.taskId,
+ assertionName = errorProto.assertionName
+ ))
+ }
+ states.add(
+ ErrorState(
+ _timestamp = stateProto.timestamp.toString(),
+ errors = errors.toTypedArray()))
+ }
+ traceParseTime += errorParseTime
+ }
+ return ErrorTrace(
+ entries = states.toTypedArray(),
+ source = source?.toString() ?: ""
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/errors/Extensions.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/errors/Extensions.kt
new file mode 100644
index 000000000..0d183ae60
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/errors/Extensions.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("Extensions")
+
+package com.android.server.wm.traces.parser.errors
+
+import android.util.Log
+import com.android.server.wm.flicker.FlickerErrorProto
+import com.android.server.wm.flicker.FlickerErrorStateProto
+import com.android.server.wm.flicker.FlickerErrorTraceProto
+import com.android.server.wm.traces.common.errors.ErrorState
+import com.android.server.wm.traces.common.errors.ErrorTrace
+import com.android.server.wm.traces.common.errors.Error
+import com.android.server.wm.traces.parser.LOG_TAG
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Path
+
+/**
+ * Stores the error trace in a .winscope file.
+ */
+fun ErrorTrace.writeToFile(outputFile: Path) {
+ val proto = FlickerErrorTraceProto
+ .newBuilder()
+ .addAllStates(this.entries.map { it.toProto() })
+ .setMagicNumber(
+ FlickerErrorTraceProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ FlickerErrorTraceProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ )
+ .build()
+ val errorTraceBytes = proto.toByteArray()
+
+ try {
+ Log.d(LOG_TAG, outputFile.toString())
+ Files.createDirectories(outputFile.parent)
+ Files.write(outputFile, errorTraceBytes)
+ } catch (e: IOException) {
+ throw RuntimeException("Unable to create error trace file: ${e.message}", e)
+ }
+}
+
+fun ErrorState.toProto(): FlickerErrorStateProto = FlickerErrorStateProto
+ .newBuilder()
+ .addAllErrors(this.errors.map { it.toProto() })
+ .setTimestamp(this.timestamp)
+ .build()
+
+fun Error.toProto(): FlickerErrorProto = FlickerErrorProto
+ .newBuilder()
+ .setStacktrace(this.stacktrace)
+ .setMessage(this.message)
+ .setLayerId(this.layerId)
+ .setWindowToken(this.windowToken)
+ .setTaskId(this.taskId)
+ .setAssertionName(this.assertionName)
+ .build() \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt
index 5bf48bfc4..bd473e5d9 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt
@@ -17,8 +17,10 @@
package com.android.server.wm.traces.parser.layers
import android.graphics.Rect
+import android.surfaceflinger.nano.Common.RectProto
+import android.surfaceflinger.nano.Common.SizeProto
+import android.surfaceflinger.nano.Display.DisplayProto
import android.surfaceflinger.nano.Layers
-import android.surfaceflinger.nano.Layers.RectProto
import android.surfaceflinger.nano.Layers.RegionProto
import android.surfaceflinger.nano.Layerstrace
import android.util.Log
@@ -26,6 +28,8 @@ import com.android.server.wm.traces.common.Buffer
import com.android.server.wm.traces.common.Color
import com.android.server.wm.traces.common.RectF
import com.android.server.wm.traces.common.Region
+import com.android.server.wm.traces.common.Size
+import com.android.server.wm.traces.common.layers.Display
import com.android.server.wm.traces.common.layers.Layer
import com.android.server.wm.traces.common.layers.LayerTraceEntry
import com.android.server.wm.traces.common.layers.LayerTraceEntryBuilder
@@ -33,6 +37,7 @@ import com.android.server.wm.traces.common.layers.LayersTrace
import com.android.server.wm.traces.parser.LOG_TAG
import com.google.protobuf.nano.InvalidProtocolBufferNanoException
import java.nio.file.Path
+import kotlin.math.max
import kotlin.system.measureTimeMillis
/**
@@ -46,7 +51,6 @@ class LayersTraceParser {
*
* @param data binary proto data
* @param source Path to source of data for additional debug information
- * @param sourceChecksum Checksum of the source file
* @param orphanLayerCallback a callback to handle any unexpected orphan layers
*/
@JvmOverloads
@@ -54,10 +58,9 @@ class LayersTraceParser {
fun parseFromTrace(
data: ByteArray,
source: Path? = null,
- sourceChecksum: String = "",
orphanLayerCallback: ((Layer) -> Boolean)? = null
): LayersTrace {
- val fileProto: Layerstrace.LayersTraceFileProto
+ var fileProto: Layerstrace.LayersTraceFileProto? = null
try {
measureTimeMillis {
fileProto = Layerstrace.LayersTraceFileProto.parseFrom(data)
@@ -67,7 +70,9 @@ class LayersTraceParser {
} catch (e: Exception) {
throw RuntimeException(e)
}
- return parseFromTrace(fileProto, source, sourceChecksum, orphanLayerCallback)
+ return fileProto?.let {
+ parseFromTrace(it, source, orphanLayerCallback)
+ } ?: error("Unable to read trace file")
}
/**
@@ -76,7 +81,6 @@ class LayersTraceParser {
*
* @param proto Parsed proto data
* @param source Path to source of data for additional debug information
- * @param sourceChecksum Checksum of the source file
* @param orphanLayerCallback a callback to handle any unexpected orphan layers
*/
@JvmOverloads
@@ -84,7 +88,6 @@ class LayersTraceParser {
fun parseFromTrace(
proto: Layerstrace.LayersTraceFileProto,
source: Path? = null,
- sourceChecksum: String = "",
orphanLayerCallback: ((Layer) -> Boolean)? = null
): LayersTrace {
val entries: MutableList<LayerTraceEntry> = ArrayList()
@@ -92,15 +95,16 @@ class LayersTraceParser {
for (traceProto: Layerstrace.LayersTraceProto in proto.entry) {
val entryParseTime = measureTimeMillis {
val entry = newEntry(
- traceProto.elapsedRealtimeNanos, traceProto.layers.layers,
- traceProto.hwcBlob, traceProto.where, orphanLayerCallback)
+ traceProto.elapsedRealtimeNanos, traceProto.displays,
+ traceProto.layers.layers, traceProto.hwcBlob, traceProto.where,
+ orphanLayerCallback)
entries.add(entry)
}
traceParseTime += entryParseTime
}
Log.v(LOG_TAG, "Parsing duration (Layers Trace): ${traceParseTime}ms " +
- "(avg ${traceParseTime / entries.size}ms per entry)")
- return LayersTrace(entries, source?.toString() ?: "", sourceChecksum)
+ "(avg ${traceParseTime / max(entries.size, 1)}ms per entry)")
+ return LayersTrace(entries.toTypedArray(), source?.toString() ?: "")
}
/**
@@ -110,8 +114,11 @@ class LayersTraceParser {
* @param proto Parsed proto data
*/
@JvmStatic
- fun parseFromDump(proto: Layers.LayersProto): LayersTrace {
- val entry = newEntry(timestamp = 0, protos = proto.layers)
+ @Deprecated("This functions parsers old SF dumps. Now SF dumps create a " +
+ "single entry trace, for new dump use [parseFromTrace]")
+ fun parseFromLegacyDump(proto: Layers.LayersProto): LayersTrace {
+ val entry = newEntry(timestamp = 0, displayProtos = emptyArray(),
+ protos = proto.layers)
return LayersTrace(entry)
}
@@ -122,25 +129,29 @@ class LayersTraceParser {
* @param data binary proto data
*/
@JvmStatic
- fun parseFromDump(data: ByteArray?): LayersTrace {
+ @Deprecated("This functions parsers old SF dumps. Now SF dumps create a " +
+ "single entry trace, for new dump use [parseFromTrace]")
+ fun parseFromLegacyDump(data: ByteArray?): LayersTrace {
val traceProto = try {
Layers.LayersProto.parseFrom(data)
} catch (e: InvalidProtocolBufferNanoException) {
throw RuntimeException(e)
}
- return parseFromDump(traceProto)
+ return parseFromLegacyDump(traceProto)
}
@JvmStatic
private fun newEntry(
timestamp: Long,
+ displayProtos: Array<DisplayProto>,
protos: Array<Layers.LayerProto>,
hwcBlob: String = "",
where: String = "",
orphanLayerCallback: ((Layer) -> Boolean)? = null
): LayerTraceEntry {
- val layers = protos.map { newLayer(it) }
- val builder = LayerTraceEntryBuilder(timestamp, layers, hwcBlob, where)
+ val layers = protos.map { newLayer(it) }.toTypedArray()
+ val displays = displayProtos.map { newDisplay(it) }.toTypedArray()
+ val builder = LayerTraceEntryBuilder(timestamp, layers, displays, hwcBlob, where)
builder.setOrphanLayerCallback(orphanLayerCallback)
return builder.build()
}
@@ -171,7 +182,7 @@ class LayersTraceParser {
proto.type ?: "",
proto.screenBounds?.toRectF(),
Transform(proto.transform, proto.position),
- proto.sourceBounds?.toRectF(),
+ proto.sourceBounds?.toRectF() ?: RectF.EMPTY,
proto.currFrame,
proto.effectiveScalingMode,
Transform(proto.bufferTransform, position = null),
@@ -185,6 +196,17 @@ class LayersTraceParser {
)
}
+ private fun newDisplay(proto: DisplayProto): Display {
+ return Display(
+ proto.id.toULong(),
+ proto.name,
+ proto.layerStack,
+ proto.size.toSize(),
+ proto.layerStackSpaceRect.toRect(),
+ Transform(proto.transform, position = null)
+ )
+ }
+
@JvmStatic
private fun Layers.FloatRectProto?.toRectF(): RectF? {
return this?.let {
@@ -193,6 +215,13 @@ class LayersTraceParser {
}
@JvmStatic
+ private fun SizeProto?.toSize(): Size {
+ return this?.let {
+ Size(this.w, this.h)
+ } ?: Size.EMPTY
+ }
+
+ @JvmStatic
private fun Layers.ColorProto?.toColor(): Color {
if (this == null) {
return Color.EMPTY
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt
index ca1b28fdf..856ebb2eb 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.traces.parser.layers
import android.surfaceflinger.nano.Layers
+import android.surfaceflinger.nano.Common.TransformProto
import com.android.server.wm.traces.common.layers.Transform
import com.android.server.wm.traces.common.layers.Transform.Companion.FLIP_H_VAL
import com.android.server.wm.traces.common.layers.Transform.Companion.FLIP_V_VAL
@@ -26,13 +27,13 @@ import com.android.server.wm.traces.common.layers.Transform.Companion.SCALE_VAL
import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagClear
import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagSet
-class Transform(transform: Layers.TransformProto?, position: Layers.PositionProto?) :
+class Transform(transform: TransformProto?, position: Layers.PositionProto?) :
Transform(
transform?.type,
getMatrix(transform, position)
)
-private fun getMatrix(transform: Layers.TransformProto?, position: Layers.PositionProto?):
+private fun getMatrix(transform: TransformProto?, position: Layers.PositionProto?):
Transform.Matrix {
val x = position?.x ?: 0f
val y = position?.y ?: 0f
@@ -65,4 +66,4 @@ private fun Int?.getDefaultTransform(x: Float, y: Float): Transform.Matrix {
else ->
throw IllegalStateException("Unknown transform type $this")
}
-} \ No newline at end of file
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/tags/Extensions.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/tags/Extensions.kt
new file mode 100644
index 000000000..287758011
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/tags/Extensions.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("Extensions")
+
+package com.android.server.wm.traces.parser.tags
+
+import android.util.Log
+import com.android.server.wm.flicker.FlickerTagProto
+import com.android.server.wm.flicker.FlickerTagStateProto
+import com.android.server.wm.flicker.FlickerTagTraceProto
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.TagState
+import com.android.server.wm.traces.common.tags.TagTrace
+import com.android.server.wm.traces.parser.LOG_TAG
+import java.nio.file.Files
+import java.nio.file.Path
+
+fun TagTrace.writeToFile(outputFile: Path) {
+ val proto = FlickerTagTraceProto
+ .newBuilder()
+ .addAllStates(this.entries.map { it.toProto() })
+ .setMagicNumber(
+ FlickerTagTraceProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ FlickerTagTraceProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ )
+ .build()
+
+ val tagTraceBytes = proto.toByteArray()
+
+ try {
+ Log.d(LOG_TAG, outputFile.toString())
+ Files.createDirectories(outputFile.parent)
+ Files.write(outputFile, tagTraceBytes)
+ } catch (e: Exception) {
+ throw RuntimeException("Unable to create error trace file: ${e.message}", e)
+ }
+}
+
+fun TagState.toProto(): FlickerTagStateProto = FlickerTagStateProto
+ .newBuilder()
+ .addAllTags(this.tags.map { it.toProto() })
+ .setTimestamp(this.timestamp)
+ .build()
+
+fun Tag.toProto(): FlickerTagProto = FlickerTagProto
+ .newBuilder()
+ .setIsStartTag(this.isStartTag)
+ .setTransition(FlickerTagProto.Transition.valueOf(this.transition.name))
+ .setId(this.id)
+ .setTaskId(this.taskId)
+ .setWindowToken(this.windowToken)
+ .setLayerId(this.layerId)
+ .build()
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/tags/TagTraceParserUtil.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/tags/TagTraceParserUtil.kt
new file mode 100644
index 000000000..1dd346c37
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/tags/TagTraceParserUtil.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.traces.parser.tags
+
+import android.util.Log
+import com.android.server.wm.flicker.FlickerTagTraceProto
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.TagState
+import com.android.server.wm.traces.common.tags.TagTrace
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.parser.LOG_TAG
+import java.nio.file.Path
+import kotlin.system.measureTimeMillis
+
+/**
+ * Class that holds the methods to parse tag proto files into tag classes.
+ */
+class TagTraceParserUtil {
+ companion object {
+ /**
+ * Parses [FlickerTagTraceProto] from [data] and uses the proto to generates a list
+ * of trace entries, storing the flattened layers into its hierarchical structure.
+ *
+ * @param data binary proto data
+ * @param source Path to source of data for additional debug information
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun parseFromTrace(
+ data: ByteArray,
+ source: Path? = null
+ ): TagTrace {
+ var fileProto: FlickerTagTraceProto? = null
+ try {
+ measureTimeMillis {
+ fileProto = FlickerTagTraceProto.parseFrom(data)
+ }.also {
+ Log.v(LOG_TAG, "Parsing proto (Flicker Tags Trace): ${it}ms")
+ }
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+ return fileProto?.let {
+ parseFromTrace(it)
+ } ?: error("Unable to read flicker tags trace file")
+ }
+
+ /**
+ * Parses [FlickerTagTraceProto] from [proto] and uses the proto to generates a list
+ * of trace entries, storing the flattened layers into its hierarchical structure.
+ *
+ * @param proto Parsed proto data
+ * @param source Path to source of data for additional debug informationy
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun parseFromTrace(
+ proto: FlickerTagTraceProto,
+ source: Path? = null
+ ): TagTrace {
+ val states = mutableListOf<TagState>()
+ var traceParseTime = 0L
+ for (stateProto in proto.statesList) {
+ val tagParseTime = measureTimeMillis {
+ val tags = mutableListOf<Tag>()
+ for (tagProto in stateProto.tagsList) {
+ tags.add(
+ Tag(
+ layerId = tagProto.layerId,
+ windowToken = tagProto.windowToken,
+ taskId = tagProto.taskId,
+ transition = Transition.valueOf(tagProto.transition.name),
+ id = tagProto.id,
+ isStartTag = tagProto.isStartTag
+ ))
+ }
+ states.add(
+ TagState(
+ _timestamp = stateProto.timestamp.toString(),
+ tags = tags.toTypedArray()))
+ }
+ traceParseTime += tagParseTime
+ }
+ return TagTrace(
+ entries = states.toTypedArray(),
+ source = source?.toString() ?: ""
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt
index 1a0b23c58..8889fd0c0 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt
@@ -18,13 +18,11 @@ package com.android.server.wm.traces.parser.windowmanager
import android.app.ActivityTaskManager
import android.app.WindowConfiguration
-import android.content.ComponentName
-import com.android.server.wm.traces.parser.toActivityName
-import com.android.server.wm.traces.parser.toWindowName
+import com.android.server.wm.traces.common.FlickerComponentName
data class WaitForValidActivityState(
@JvmField
- val activityName: ComponentName?,
+ val activityName: FlickerComponentName?,
@JvmField
val windowName: String?,
@JvmField
@@ -34,7 +32,7 @@ data class WaitForValidActivityState(
@JvmField
val activityType: Int
) {
- constructor(activityName: ComponentName) : this(
+ constructor(activityName: FlickerComponentName) : this(
activityName,
windowName = activityName.toWindowName(),
stackId = ActivityTaskManager.INVALID_STACK_ID,
@@ -70,7 +68,7 @@ data class WaitForValidActivityState(
return sb.toString()
}
- class Builder constructor(internal var activityName: ComponentName? = null) {
+ class Builder constructor(internal var activityName: FlickerComponentName? = null) {
internal var windowName: String? = activityName?.toWindowName()
internal var stackId = ActivityTaskManager.INVALID_STACK_ID
internal var windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt
index 3d4b08395..522915bd5 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt
@@ -19,25 +19,26 @@ package com.android.server.wm.traces.parser.windowmanager
import android.app.ActivityTaskManager
import android.app.Instrumentation
import android.app.WindowConfiguration
-import android.content.ComponentName
import android.graphics.Rect
import android.graphics.Region
import android.os.SystemClock
import android.util.Log
import android.view.Display
-import androidx.annotation.VisibleForTesting
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.parser.getCurrentStateDump
import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import com.android.server.wm.traces.parser.Condition
+import com.android.server.wm.traces.common.Condition
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.IME
import com.android.server.wm.traces.parser.LOG_TAG
-import com.android.server.wm.traces.parser.toActivityName
+import com.android.server.wm.traces.common.WaitCondition
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.parser.getCurrentStateDump
import com.android.server.wm.traces.parser.toAndroidRegion
-import com.android.server.wm.traces.parser.toWindowName
open class WindowManagerStateHelper @JvmOverloads constructor(
/**
@@ -47,53 +48,51 @@ open class WindowManagerStateHelper @JvmOverloads constructor(
/**
* Predicate to supply a new UI information
*/
- private val deviceDumpSupplier: () -> Dump = {
- val currState = getCurrentStateDump(
- instrumentation.uiAutomation)
- Dump(
- currState.wmTrace?.entries?.first() ?: error("Unable to parse WM trace"),
- currState.layersTrace?.entries?.first() ?: error("Unable to parse Layers trace")
+ private val deviceDumpSupplier: () -> DeviceStateDump<WindowManagerState, LayerTraceEntry> = {
+ val currState = getCurrentStateDump(instrumentation.uiAutomation)
+ DeviceStateDump(
+ currState.wmState ?: error("Unable to parse WM trace"),
+ currState.layerState ?: error("Unable to parse Layers trace")
)
},
/**
* Number of attempts to satisfy a wait condition
*/
- private val numRetries: Int = 5,
+ private val numRetries: Int = WaitCondition.DEFAULT_RETRY_LIMIT,
/**
* Interval between wait for state dumps during wait conditions
*/
- private val retryIntervalMs: Long = 500L
+ private val retryIntervalMs: Long = WaitCondition.DEFAULT_RETRY_INTERVAL_MS
) {
- /**
- * Fetches the current device state
- */
- val currentState: Dump
- get() = computeState(ignoreInvalidStates = true)
+ private var internalState: DeviceStateDump<WindowManagerState, LayerTraceEntry>? = null
/**
* Queries the supplier for a new device state
- *
- * @param ignoreInvalidStates If false, retries up to [numRetries] times (with a sleep
- * interval of [retryIntervalMs] ms to obtain a complete WM state, otherwise returns the
- * first state
*/
- protected open fun computeState(ignoreInvalidStates: Boolean = false): Dump {
- var newState = deviceDumpSupplier.invoke()
- for (retryNr in 0..numRetries) {
- val wmState = newState.wmState
- if (!ignoreInvalidStates && wmState.isIncomplete()) {
- Log.w(LOG_TAG, "***Incomplete AM state: ${wmState.getIsIncompleteReason()}" +
- " Waiting ${retryIntervalMs}ms and retrying ($retryNr/$numRetries)...")
- SystemClock.sleep(retryIntervalMs)
- newState = deviceDumpSupplier.invoke()
+ val currentState: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ get() {
+ if (internalState == null) {
+ internalState = deviceDumpSupplier.invoke()
} else {
- break
+ waitForValidState()
}
- }
+ return internalState ?: error("Unable to fetch an internal state")
+ }
- return newState
+ protected open fun updateCurrState(
+ value: DeviceStateDump<WindowManagerState, LayerTraceEntry>
+ ) {
+ internalState = value
}
+ private fun createConditionBuilder():
+ WaitCondition.Builder<DeviceStateDump<WindowManagerState, LayerTraceEntry>> =
+ WaitCondition.Builder(deviceDumpSupplier, numRetries)
+ .onSuccess { updateCurrState(it) }
+ .onFailure { updateCurrState(it) }
+ .onLog { Log.d(LOG_TAG, it) }
+ .onRetry { SystemClock.sleep(retryIntervalMs) }
+
private fun ConfigurationContainer.isWindowingModeCompatible(
requestedWindowingMode: Int
): Boolean {
@@ -111,16 +110,15 @@ open class WindowManagerStateHelper @JvmOverloads constructor(
* @param waitForActivitiesVisible array of activity states to wait for.
*/
fun waitForValidState(vararg waitForActivitiesVisible: WaitForValidActivityState): Boolean {
- val success = Condition.waitFor<WindowManagerState>("valid stacks and activities states",
- retryLimit = numRetries, retryIntervalMs = retryIntervalMs) {
- // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
- // requesting dump in some intermediate state.
- val state = computeState()
- !(shouldWaitForValidityCheck(state) ||
- shouldWaitForValidStacks(state) ||
- shouldWaitForActivities(state, *waitForActivitiesVisible) ||
- shouldWaitForWindows(state))
+ val builder = createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isWMStateComplete())
+
+ if (waitForActivitiesVisible.isNotEmpty()) {
+ builder.withCondition("!shouldWaitForActivities") {
+ !shouldWaitForActivities(it, *waitForActivitiesVisible)
+ }
}
+ val success = builder.build().waitFor()
if (!success) {
Log.e(LOG_TAG, "***Waiting for states failed: " +
waitForActivitiesVisible.contentToString())
@@ -128,225 +126,140 @@ open class WindowManagerStateHelper @JvmOverloads constructor(
return success
}
- fun waitForFullScreenApp(componentName: ComponentName): Boolean =
+ fun waitForFullScreenApp(component: FlickerComponentName): Boolean =
waitForValidState(
WaitForValidActivityState
- .Builder(componentName)
+ .Builder(component)
.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
.setActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
.build())
- fun waitForHomeActivityVisible(): Boolean {
- return waitFor { it.wmState.homeActivity?.isVisible == true } &&
- waitForNavBarStatusBarVisible() &&
- waitForAppTransitionIdle()
- }
+ fun waitForHomeActivityVisible(): Boolean =
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isHomeActivityVisible())
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .withCondition(WindowManagerConditionsFactory.isNavBarVisible())
+ .withCondition(WindowManagerConditionsFactory.isStatusBarVisible())
+ .build()
+ .waitFor()
fun waitForRecentsActivityVisible(): Boolean =
- waitFor("recents activity to be visible") {
- it.wmState.isRecentsActivityVisible
- }
-
- fun waitForAodShowing(): Boolean =
- waitFor("AOD showing") {
- it.wmState.keyguardControllerState.isAodShowing
- }
-
- fun waitForKeyguardGone(): Boolean =
- waitFor("Keyguard gone") {
- !it.wmState.keyguardControllerState.isKeyguardShowing
- }
+ createConditionBuilder()
+ .withCondition("isRecentsActivityVisible") {
+ it.wmState.isRecentsActivityVisible
+ }
+ .build()
+ .waitFor()
/**
* Wait for specific rotation for the default display. Values are Surface#Rotation
*/
@JvmOverloads
- fun waitForRotation(rotation: Int, displayId: Int = Display.DEFAULT_DISPLAY): Boolean =
- waitFor("Rotation: $rotation") {
- val currRotation = it.wmState.getRotation(displayId)
- val rotationLayerExists = it.layerState.isVisible(ROTATION_LAYER_NAME)
- val blackSurfaceLayerExists = it.layerState.isVisible(BLACK_SURFACE_LAYER_NAME)
- val anyLayerAnimating = it.layerState.visibleLayers.any { layer ->
- !layer.transform.isSimpleRotation
+ fun waitForRotation(rotation: Int, displayId: Int = Display.DEFAULT_DISPLAY): Boolean {
+ val hasRotationCondition = WindowManagerConditionsFactory.hasRotation(rotation, displayId)
+ return createConditionBuilder()
+ .withCondition("waitForRotation[$rotation]") {
+ if (!it.wmState.canRotate) {
+ Log.v(LOG_TAG, "Rotation is not allowed in the state")
+ true
+ } else {
+ hasRotationCondition.isSatisfied(it)
+ }
}
- Log.v(LOG_TAG, "currRotation($currRotation) " +
- "anyLayerAnimating($anyLayerAnimating) " +
- "blackSurfaceLayerExists($blackSurfaceLayerExists) " +
- "rotationLayerExists($rotationLayerExists)")
- currRotation == rotation &&
- !anyLayerAnimating &&
- !rotationLayerExists &&
- !blackSurfaceLayerExists
- }
-
- /**
- * Wait for specific orientation for the default display.
- * Values are ActivityInfo.ScreenOrientation
- */
- @JvmOverloads
- fun waitForLastOrientation(
- orientation: Int,
- displayId: Int = Display.DEFAULT_DISPLAY
- ): Boolean =
- waitFor("LastOrientation: $orientation") {
- val result = it.wmState.getOrientation(displayId)
- Log.v(LOG_TAG, "Current: $result Expected: $orientation")
- result == orientation
- }
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
+ }
- fun waitForActivityState(activity: ComponentName, activityState: String): Boolean {
+ fun waitForActivityState(activity: FlickerComponentName, activityState: String): Boolean {
val activityName = activity.toActivityName()
- return waitFor("state of $activityName to be $activityState") {
- it.wmState.hasActivityState(activityName, activityState)
- }
+ return createConditionBuilder()
+ .withCondition("state of $activityName to be $activityState") {
+ it.wmState.hasActivityState(activityName, activityState)
+ }
+ .build()
+ .waitFor()
}
/**
* Waits until the navigation and status bars are visible (windows and layers)
*/
fun waitForNavBarStatusBarVisible(): Boolean =
- waitFor("Navigation and Status bar to be visible") {
- val navBarWindowVisible = it.wmState.isWindowVisible(NAV_BAR_WINDOW_NAME)
- val statusBarWindowVisible = it.wmState.isWindowVisible(STATUS_BAR_WINDOW_NAME)
- val navBarLayerVisible = it.layerState.isVisible(NAV_BAR_LAYER_NAME)
- val navBarLayerAlpha = it.layerState.getLayerWithBuffer(NAV_BAR_LAYER_NAME)
- ?.color?.a ?: 0f
- val statusBarLayerVisible = it.layerState.isVisible(STATUS_BAR_LAYER_NAME)
- val statusBarLayerAlpha = it.layerState.getLayerWithBuffer(STATUS_BAR_LAYER_NAME)
- ?.color?.a ?: 0f
- val result = navBarWindowVisible &&
- navBarLayerVisible &&
- statusBarWindowVisible &&
- statusBarLayerVisible &&
- navBarLayerAlpha == 1f &&
- statusBarLayerAlpha == 1f
-
- Log.v(LOG_TAG, "Current $result " +
- "navBarWindowVisible($navBarWindowVisible) " +
- "navBarLayerVisible($navBarLayerVisible) " +
- "statusBarWindowVisible($statusBarWindowVisible) " +
- "statusBarLayerVisible($statusBarLayerVisible) " +
- "navBarLayerAlpha($navBarLayerAlpha) " +
- "statusBarLayerAlpha($statusBarLayerAlpha)")
-
- result
- }
-
- fun waitForVisibleWindow(activity: ComponentName): Boolean {
- val activityName = activity.toActivityName()
- val windowName = activity.toWindowName()
- return waitFor("$activityName to exist") {
- val containsActivity = it.wmState.containsActivity(activityName)
- val containsWindow = it.wmState.containsWindow(windowName)
- val activityVisible = containsActivity && it.wmState.isActivityVisible(activityName)
- val windowVisible = containsWindow && it.wmState.isWindowSurfaceShown(windowName)
- val result = containsActivity &&
- containsWindow &&
- activityVisible &&
- windowVisible
-
- Log.v(LOG_TAG, "Current: $result " +
- "containsActivity($containsActivity) " +
- "containsWindow($containsWindow) " +
- "activityVisible($activityVisible) " +
- "windowVisible($windowVisible)")
-
- result
- }
- }
-
- fun waitForActivityRemoved(activity: ComponentName): Boolean {
- val activityName = activity.toActivityName()
- val windowName = activity.toWindowName()
- return waitFor("$activityName to be removed") {
- val containsActivity = it.wmState.containsActivity(activityName)
- val containsWindow = it.wmState.containsWindow(windowName)
- val result = !containsActivity && !containsWindow
-
- Log.v(LOG_TAG, "Current: $result" +
- "containsActivity($containsActivity)" +
- "containsWindow($containsWindow)")
- result
- }
- }
-
- fun waitForPendingActivityContain(activity: ComponentName): Boolean {
- val activityName: String = activity.toActivityName()
- return waitFor("$activityName in pending list") {
- it.wmState.pendingActivityContain(activityName)
- }
- }
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isNavBarVisible())
+ .withCondition(WindowManagerConditionsFactory.isStatusBarVisible())
+ .build()
+ .waitFor()
+
+ fun waitForVisibleWindow(component: FlickerComponentName): Boolean =
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.containsActivity(component))
+ .withCondition(WindowManagerConditionsFactory.containsWindow(component))
+ .withCondition(WindowManagerConditionsFactory.isActivityVisible(component))
+ .withCondition(WindowManagerConditionsFactory.isWindowSurfaceShown(component))
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
+
+ fun waitForActivityRemoved(component: FlickerComponentName): Boolean =
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.containsActivity(component).negate())
+ .withCondition(WindowManagerConditionsFactory.containsWindow(component).negate())
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
@JvmOverloads
fun waitForAppTransitionIdle(displayId: Int = Display.DEFAULT_DISPLAY): Boolean =
- waitFor("app transition idle on Display $displayId") {
- val result =
- it.wmState.getDisplay(displayId)?.appTransitionState
- Log.v(LOG_TAG, "Current: $result")
- WindowManagerState.APP_STATE_IDLE == result
- }
-
- fun waitForWindowSurfaceDisappeared(componentName: ComponentName): Boolean {
- val windowName = componentName.toWindowName()
- return waitFor("$windowName's surface is disappeared") {
- !it.wmState.isWindowSurfaceShown(windowName)
- }
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isAppTransitionIdle(displayId))
+ .build()
+ .waitFor()
+
+ fun waitForWindowSurfaceDisappeared(component: FlickerComponentName): Boolean {
+ val condition = WindowManagerConditionsFactory.isWindowSurfaceShown(component).negate()
+ return createConditionBuilder()
+ .withCondition(condition)
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
}
- fun waitForSurfaceAppeared(surfaceName: String): Boolean {
- return waitFor("$surfaceName surface is appeared") {
- it.wmState.isWindowSurfaceShown(surfaceName)
- }
- }
+ fun waitForSurfaceAppeared(surfaceName: String): Boolean =
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isWindowSurfaceShown(surfaceName))
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
- fun waitWindowingModeTopFocus(
- windowingMode: Int,
- topFocus: Boolean,
- message: String
- ): Boolean = waitFor(message) {
- val stack = it.wmState.getStandardStackByWindowingMode(windowingMode)
- (stack != null && topFocus == (it.wmState.focusedStackId == stack.rootTaskId))
+ fun waitFor(
+ vararg conditions: Condition<DeviceStateDump<WindowManagerState, LayerTraceEntry>>
+ ): Boolean {
+ val builder = createConditionBuilder()
+ conditions.forEach { builder.withCondition(it) }
+ return builder.build().waitFor()
}
@JvmOverloads
fun waitFor(
message: String = "",
- waitCondition: (Dump) -> Boolean
- ): Boolean = Condition.waitFor<Dump>(message, retryLimit = numRetries,
- retryIntervalMs = retryIntervalMs) {
- val state = computeState()
- waitCondition.invoke(state)
- }
-
- /**
- * @return true if should wait for valid stacks state.
- */
- private fun shouldWaitForValidStacks(state: Dump): Boolean {
- if (state.wmState.stackCount == 0) {
- Log.i(LOG_TAG, "***stackCount=0")
- return true
- }
- if (!state.wmState.keyguardControllerState.isKeyguardShowing &&
- state.wmState.resumedActivities.isEmpty()) {
- if (!state.wmState.keyguardControllerState.isKeyguardShowing) {
- Log.i(LOG_TAG, "***resumedActivitiesCount=0")
- } else {
- Log.i(LOG_TAG, "***isKeyguardShowing=true")
- }
- return true
- }
- if (state.wmState.focusedActivity.isEmpty()) {
- Log.i(LOG_TAG, "***focusedActivity=null")
- return true
- }
- return false
- }
+ waitCondition: (DeviceStateDump<WindowManagerState, LayerTraceEntry>) -> Boolean
+ ): Boolean = createConditionBuilder()
+ .withCondition(message, waitCondition)
+ .build()
+ .waitFor()
/**
* @return true if should wait for some activities to become visible.
*/
private fun shouldWaitForActivities(
- state: Dump,
+ state: DeviceStateDump<WindowManagerState, LayerTraceEntry>,
vararg waitForActivitiesVisible: WaitForValidActivityState
): Boolean {
if (waitForActivitiesVisible.isEmpty()) {
@@ -397,65 +310,35 @@ open class WindowManagerStateHelper @JvmOverloads constructor(
}
/**
- * @return true if should wait for the valid windows state.
- */
- private fun shouldWaitForWindows(state: Dump): Boolean {
- return when {
- state.wmState.frontWindow == null -> {
- Log.i(LOG_TAG, "***frontWindow=null")
- true
- }
- state.wmState.focusedWindow.isEmpty() -> {
- Log.i(LOG_TAG, "***focusedWindow=null")
- true
- }
- state.wmState.focusedApp.isEmpty() -> {
- Log.i(LOG_TAG, "***focusedApp=null")
- true
- }
- else -> false
- }
- }
-
- private fun shouldWaitForValidityCheck(state: Dump): Boolean {
- return !state.wmState.isComplete()
- }
-
- /**
* Waits until the IME window and layer are visible
*/
@JvmOverloads
- fun waitImeWindowShown(displayId: Int = Display.DEFAULT_DISPLAY): Boolean =
- waitFor("IME window shown") {
- val imeSurfaceShown = it.wmState.inputMethodWindowState?.isSurfaceShown == true
- val imeDisplay = it.wmState.inputMethodWindowState?.displayId
- val imeLayerShown = it.layerState.isVisible(IME_LAYER_NAME)
- val result = imeSurfaceShown && imeLayerShown && imeDisplay == displayId
-
- Log.v(LOG_TAG, "Current: $result " +
- "imeSurfaceShown($imeSurfaceShown) " +
- "imeLayerShown($imeLayerShown) " +
- "imeDisplay($imeDisplay)")
- result
- }
+ fun waitImeShown(displayId: Int = Display.DEFAULT_DISPLAY): Boolean =
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isImeShown(displayId))
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
/**
* Waits until the IME layer is no longer visible. Cannot wait for the window as
* its visibility information is updated at a later state and is not reliable in
* the trace
*/
- fun waitImeWindowGone(): Boolean =
- waitFor("IME window gone") {
- val imeLayerShown = it.layerState.isVisible(IME_LAYER_NAME)
- Log.v(LOG_TAG, "imeLayerShown($imeLayerShown)")
- !imeLayerShown
- }
+ fun waitImeGone(): Boolean =
+ createConditionBuilder()
+ .withCondition(WindowManagerConditionsFactory.isLayerVisible(IME).negate())
+ .withCondition(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+ .build()
+ .waitFor()
/**
* Obtains a [WindowContainer] from the current device state, or null if the WindowContainer
* doesn't exist
*/
- fun getWindow(activity: ComponentName): WindowState? {
+ fun getWindow(activity: FlickerComponentName): WindowState? {
val windowName = activity.toWindowName()
return this.currentState.wmState.windowStates
.firstOrNull { it.title == windowName }
@@ -464,40 +347,8 @@ open class WindowManagerStateHelper @JvmOverloads constructor(
/**
* Obtains the region of a window in the state, or an empty [Rect] is there are none
*/
- fun getWindowRegion(activity: ComponentName): Region {
+ fun getWindowRegion(activity: FlickerComponentName): Region {
val window = getWindow(activity)
return window?.frameRegion?.toAndroidRegion() ?: Region()
}
-
- companion object {
- @VisibleForTesting
- const val NAV_BAR_WINDOW_NAME = "NavigationBar0"
- @VisibleForTesting
- const val STATUS_BAR_WINDOW_NAME = "StatusBar"
- @VisibleForTesting
- const val NAV_BAR_LAYER_NAME = "$NAV_BAR_WINDOW_NAME#0"
- @VisibleForTesting
- const val STATUS_BAR_LAYER_NAME = "$STATUS_BAR_WINDOW_NAME#0"
- @VisibleForTesting
- const val ROTATION_LAYER_NAME = "RotationLayer#0"
- @VisibleForTesting
- const val BLACK_SURFACE_LAYER_NAME = "BackColorSurface#0"
- @VisibleForTesting
- const val IME_LAYER_NAME = "InputMethod#0"
- @VisibleForTesting
- const val SPLASH_SCREEN_NAME = "Splash Screen"
- @VisibleForTesting
- const val SNAPSHOT_WINDOW_NAME = "SnapshotStartingWindow"
- }
-
- data class Dump(
- /**
- * Window manager state
- */
- @JvmField val wmState: WindowManagerState,
- /**
- * Layers state
- */
- @JvmField val layerState: LayerTraceEntry
- )
} \ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt
index 718b0b3ca..010f421c6 100644
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt
+++ b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt
@@ -22,42 +22,45 @@ import android.graphics.nano.RectProto
import android.util.Log
import android.view.nano.ViewProtoEnums
import android.view.nano.WindowLayoutParamsProto
-import com.android.server.wm.traces.common.windowmanager.windows.Configuration
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.windowmanager.windows.WindowConfiguration
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.ActivityTask
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayArea
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayContent
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import com.android.server.wm.traces.common.windowmanager.windows.WindowToken
import com.android.server.wm.nano.ActivityRecordProto
import com.android.server.wm.nano.AppTransitionProto
import com.android.server.wm.nano.ConfigurationContainerProto
import com.android.server.wm.nano.DisplayAreaProto
import com.android.server.wm.nano.DisplayContentProto
import com.android.server.wm.nano.KeyguardControllerProto
-import com.android.server.wm.nano.WindowManagerPolicyProto
import com.android.server.wm.nano.RootWindowContainerProto
+import com.android.server.wm.nano.TaskFragmentProto
import com.android.server.wm.nano.TaskProto
import com.android.server.wm.nano.WindowContainerChildProto
import com.android.server.wm.nano.WindowContainerProto
+import com.android.server.wm.nano.WindowManagerPolicyProto
import com.android.server.wm.nano.WindowManagerServiceDumpProto
import com.android.server.wm.nano.WindowManagerTraceFileProto
import com.android.server.wm.nano.WindowStateProto
import com.android.server.wm.nano.WindowTokenProto
-import com.android.server.wm.traces.common.Bounds
+import com.android.server.wm.traces.common.Size
+import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+import com.android.server.wm.traces.common.windowmanager.windows.Activity
+import com.android.server.wm.traces.common.windowmanager.windows.Configuration
+import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
+import com.android.server.wm.traces.common.windowmanager.windows.DisplayArea
+import com.android.server.wm.traces.common.windowmanager.windows.DisplayContent
+import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
+import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
+import com.android.server.wm.traces.common.windowmanager.windows.Task
+import com.android.server.wm.traces.common.windowmanager.windows.TaskFragment
+import com.android.server.wm.traces.common.windowmanager.windows.WindowConfiguration
+import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
import com.android.server.wm.traces.common.windowmanager.windows.WindowLayoutParams
+import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
+import com.android.server.wm.traces.common.windowmanager.windows.WindowState
+import com.android.server.wm.traces.common.windowmanager.windows.WindowToken
import com.android.server.wm.traces.parser.LOG_TAG
import com.google.protobuf.nano.InvalidProtocolBufferNanoException
import java.nio.file.Path
+import kotlin.math.max
import kotlin.system.measureTimeMillis
object WindowManagerTraceParser {
@@ -83,16 +86,14 @@ object WindowManagerTraceParser {
*
* @param data binary proto data
* @param source Path to source of data for additional debug information
- * @param checksum File SHA512 checksum
*/
@JvmOverloads
@JvmStatic
fun parseFromTrace(
data: ByteArray?,
- source: Path? = null,
- checksum: String = ""
+ source: Path? = null
): WindowManagerTrace {
- val fileProto: WindowManagerTraceFileProto
+ var fileProto: WindowManagerTraceFileProto? = null
try {
measureTimeMillis {
fileProto = WindowManagerTraceFileProto.parseFrom(data)
@@ -102,7 +103,9 @@ object WindowManagerTraceParser {
} catch (e: InvalidProtocolBufferNanoException) {
throw RuntimeException(e)
}
- return parseFromTrace(fileProto, source, checksum)
+
+ return fileProto?.let { parseFromTrace(it, source) }
+ ?: error("Unable to read trace file")
}
/**
@@ -110,14 +113,12 @@ object WindowManagerTraceParser {
*
* @param proto Parsed proto data
* @param source Path to source of data for additional debug information
- * @param checksum File SHA512 checksum
*/
@JvmOverloads
@JvmStatic
fun parseFromTrace(
proto: WindowManagerTraceFileProto,
- source: Path? = null,
- checksum: String = ""
+ source: Path? = null
): WindowManagerTrace {
val entries = mutableListOf<WindowManagerState>()
var traceParseTime = 0L
@@ -131,9 +132,8 @@ object WindowManagerTraceParser {
}
Log.v(LOG_TAG, "Parsing duration (WM Trace): ${traceParseTime}ms " +
- "(avg ${traceParseTime / entries.size}ms per entry)")
- return WindowManagerTrace(entries, source?.toAbsolutePath()?.toString()
- ?: "", checksum)
+ "(avg ${traceParseTime / max(entries.size, 1)}ms per entry)")
+ return WindowManagerTrace(entries.toTypedArray(), "${source?.toAbsolutePath()}")
}
/**
@@ -145,9 +145,8 @@ object WindowManagerTraceParser {
@JvmStatic
fun parseFromDump(proto: WindowManagerServiceDumpProto): WindowManagerTrace {
return WindowManagerTrace(
- listOf(newTraceEntry(proto, timestamp = 0, where = "")),
- source = "",
- sourceChecksum = "")
+ arrayOf(newTraceEntry(proto, timestamp = 0, where = "")),
+ source = "")
}
/**
@@ -163,7 +162,7 @@ object WindowManagerTraceParser {
} catch (e: InvalidProtocolBufferNanoException) {
throw RuntimeException(e)
}
- return WindowManagerTrace(parseFromDump(fileProto), source = "", sourceChecksum = "")
+ return parseFromDump(fileProto)
}
private fun newTraceEntry(
@@ -189,11 +188,11 @@ object WindowManagerTraceParser {
root = newRootWindowContainer(proto.rootWindowContainer),
keyguardControllerState = newKeyguardControllerState(
proto.rootWindowContainer.keyguardController),
- timestamp = timestamp
+ _timestamp = timestamp.toString()
)
}
- private fun newWindowManagerPolicy(proto: WindowManagerPolicyProto): WindowManagerPolicy? {
+ private fun newWindowManagerPolicy(proto: WindowManagerPolicyProto): WindowManagerPolicy {
return WindowManagerPolicy(
focusedAppToken = proto.focusedAppToken ?: "",
forceStatusBar = proto.forceStatusBar,
@@ -240,7 +239,9 @@ object WindowManagerTraceParser {
): WindowContainer? {
return newDisplayContent(proto.displayContent, isActivityInTree)
?: newDisplayArea(proto.displayArea, isActivityInTree)
- ?: newTask(proto.task, isActivityInTree) ?: newActivity(proto.activity)
+ ?: newTask(proto.task, isActivityInTree)
+ ?: newTaskFragment(proto.taskFragment, isActivityInTree)
+ ?: newActivity(proto.activity)
?: newWindowToken(proto.windowToken, isActivityInTree)
?: newWindowState(proto.window, isActivityInTree)
?: newWindowContainer(proto.windowContainer, children = emptyList())
@@ -258,8 +259,10 @@ object WindowManagerTraceParser {
focusedRootTaskId = proto.focusedRootTaskId,
resumedActivity = proto.resumedActivity?.title ?: "",
singleTaskInstance = proto.singleTaskInstance,
- _defaultPinnedStackBounds = proto.pinnedTaskController?.defaultBounds?.toRect(),
- _pinnedStackMovementBounds = proto.pinnedTaskController?.movementBounds?.toRect(),
+ defaultPinnedStackBounds = proto.pinnedTaskController?.defaultBounds?.toRect()
+ ?: Rect.EMPTY,
+ pinnedStackMovementBounds = proto.pinnedTaskController?.movementBounds?.toRect()
+ ?: Rect.EMPTY,
displayRect = Rect(0, 0, proto.displayInfo?.logicalWidth
?: 0, proto.displayInfo?.logicalHeight ?: 0),
appRect = Rect(0, 0, proto.displayInfo?.appWidth ?: 0,
@@ -267,7 +270,7 @@ object WindowManagerTraceParser {
?: 0),
dpi = proto.dpi,
flags = proto.displayInfo?.flags ?: 0,
- _stableBounds = proto.displayFrames?.stableBounds?.toRect(),
+ stableBounds = proto.displayFrames?.stableBounds?.toRect() ?: Rect.EMPTY,
surfaceSize = proto.surfaceSize,
focusedApp = proto.focusedApp,
lastTransition = appTransitionToString(
@@ -301,18 +304,18 @@ object WindowManagerTraceParser {
}
}
- private fun newTask(proto: TaskProto?, isActivityInTree: Boolean): ActivityTask? {
+ private fun newTask(proto: TaskProto?, isActivityInTree: Boolean): Task? {
return if (proto == null) {
null
} else {
- ActivityTask(
- activityType = proto.activityType,
+ Task(
+ activityType = proto.taskFragment?.activityType ?: proto.activityType,
isFullscreen = proto.fillsParent,
bounds = proto.bounds.toRect(),
taskId = proto.id,
rootTaskId = proto.rootTaskId,
- displayId = proto.displayId,
- _lastNonFullscreenBounds = proto.lastNonFullscreenBounds?.toRect(),
+ displayId = proto.taskFragment?.displayId ?: proto.displayId,
+ lastNonFullscreenBounds = proto.lastNonFullscreenBounds?.toRect() ?: Rect.EMPTY,
realActivity = proto.realActivity,
origActivity = proto.origActivity,
resizeMode = proto.resizeMode,
@@ -321,6 +324,30 @@ object WindowManagerTraceParser {
surfaceWidth = proto.surfaceWidth,
surfaceHeight = proto.surfaceHeight,
createdByOrganizer = proto.createdByOrganizer,
+ minWidth = proto.taskFragment?.minWidth ?: proto.minWidth,
+ minHeight = proto.taskFragment?.minHeight ?: proto.minHeight,
+ windowContainer = newWindowContainer(
+ proto.taskFragment?.windowContainer ?: proto.windowContainer,
+ if (proto.taskFragment != null) {
+ proto.taskFragment.windowContainer.children
+ .mapNotNull { p -> newWindowContainerChild(p, isActivityInTree) }
+ } else {
+ proto.windowContainer.children
+ .mapNotNull { p -> newWindowContainerChild(p, isActivityInTree) }
+ }
+ ) ?: error("Window container should not be null")
+ )
+ }
+ }
+
+ private fun newTaskFragment(proto: TaskFragmentProto?, isActivityInTree: Boolean):
+ TaskFragment? {
+ return if (proto == null) {
+ null
+ } else {
+ TaskFragment(
+ activityType = proto.activityType,
+ displayId = proto.displayId,
minWidth = proto.minWidth,
minHeight = proto.minHeight,
windowContainer = newWindowContainer(
@@ -385,16 +412,16 @@ object WindowManagerTraceParser {
WindowState.WINDOW_TYPE_STARTING
else -> 0
},
- requestedSize = Bounds(proto.requestedWidth, proto.requestedHeight),
+ requestedSize = Size(proto.requestedWidth, proto.requestedHeight),
surfacePosition = proto.surfacePosition?.toRect(),
- _frame = proto.windowFrames?.frame?.toRect(),
- _containingFrame = proto.windowFrames?.containingFrame?.toRect(),
- _parentFrame = proto.windowFrames?.parentFrame?.toRect(),
- _contentFrame = proto.windowFrames?.contentFrame?.toRect(),
- _contentInsets = proto.windowFrames?.contentInsets?.toRect(),
- _surfaceInsets = proto.surfaceInsets?.toRect(),
- _givenContentInsets = proto.givenContentInsets?.toRect(),
- _crop = proto.animator?.lastClipRect?.toRect(),
+ frame = proto.windowFrames?.frame?.toRect() ?: Rect.EMPTY,
+ containingFrame = proto.windowFrames?.containingFrame?.toRect() ?: Rect.EMPTY,
+ parentFrame = proto.windowFrames?.parentFrame?.toRect() ?: Rect.EMPTY,
+ contentFrame = proto.windowFrames?.contentFrame?.toRect() ?: Rect.EMPTY,
+ contentInsets = proto.windowFrames?.contentInsets?.toRect() ?: Rect.EMPTY,
+ surfaceInsets = proto.surfaceInsets?.toRect() ?: Rect.EMPTY,
+ givenContentInsets = proto.givenContentInsets?.toRect() ?: Rect.EMPTY,
+ crop = proto.animator?.lastClipRect?.toRect() ?: Rect.EMPTY,
windowContainer = newWindowContainer(
proto.windowContainer,
proto.windowContainer.children
@@ -504,7 +531,8 @@ object WindowManagerTraceParser {
_isVisible = proto.visible,
configurationContainer = newConfigurationContainer(
proto.configurationContainer),
- children.toTypedArray()
+ layerId = proto.surfaceControl?.layerId ?: 0,
+ children = children.toTypedArray()
)
}
}
@@ -551,4 +579,4 @@ object WindowManagerTraceParser {
}
private fun RectProto.toRect() = Rect(this.left, this.top, this.right, this.bottom)
-} \ No newline at end of file
+}
diff --git a/libraries/flicker/test/Android.bp b/libraries/flicker/test/Android.bp
index 1af44fe2c..314f4aa32 100644
--- a/libraries/flicker/test/Android.bp
+++ b/libraries/flicker/test/Android.bp
@@ -29,6 +29,9 @@ android_test {
test_suites: ["device-tests"],
srcs: ["src/**/*.kt"],
libs: ["android.test.runner"],
+ optimize: {
+ enabled: false
+ },
static_libs: [
"flickerlib",
"launcher-aosp-tapl"
diff --git a/libraries/flicker/test/AndroidManifest.xml b/libraries/flicker/test/AndroidManifest.xml
index 20b8f0a52..27e1dad86 100644
--- a/libraries/flicker/test/AndroidManifest.xml
+++ b/libraries/flicker/test/AndroidManifest.xml
@@ -20,6 +20,10 @@
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
<!-- Enable / Disable tracing !-->
<uses-permission android:name="android.permission.DUMP" />
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<!-- Allow the test to write directly to /sdcard/ -->
<application android:label="FlickerLibTest"
android:requestLegacyExternalStorage="true">
diff --git a/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsSurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsSurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..3648f0459
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsSurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsTagTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsTagTrace.winscope
new file mode 100644
index 000000000..2c3cb1dab
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsTagTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsWindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsWindowManagerTrace.winscope
new file mode 100644
index 000000000..a6d57f3a9
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/AppLaunchAndRotationsWindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerInvalidTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerInvalidTrace.winscope
new file mode 100644
index 000000000..d45d04a14
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerInvalidTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..92d3d76a3
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/appLaunch/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerInvalidTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerInvalidTrace.winscope
new file mode 100644
index 000000000..fe3a6acc4
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerInvalidTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerTrace.winscope
new file mode 100644
index 000000000..91538fbae
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/appLaunch/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/assertionsConfig.json b/libraries/flicker/test/assets/testdata/assertors/assertionsConfig.json
new file mode 100644
index 000000000..2350b7504
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/assertionsConfig.json
@@ -0,0 +1,120 @@
+{
+ "assertors": [
+ {
+ "transition": "ROTATION",
+ "assertions": {
+ "presubmit": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtEnd",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsInvisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsInvisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleWindowsShownMoreThanOneConsecutiveEntry"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleLayersShownMoreThanOneConsecutiveEntry"
+ }
+ ],
+ "postsubmit": [],
+ "flaky": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAlways"
+ }
+ ]
+ }
+ },
+ {
+ "transition": "APP_LAUNCH",
+ "assertions": {
+ "presubmit": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAlways",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleWindowsShownMoreThanOneConsecutiveEntry"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtEnd",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NavBarLayerPositionAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleLayersShownMoreThanOneConsecutiveEntry"
+ }
+ ],
+ "postsubmit": [],
+ "flaky": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAlways"
+ }
+ ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/libraries/flicker/test/assets/testdata/assertors/config.json b/libraries/flicker/test/assets/testdata/assertors/config.json
new file mode 100644
index 000000000..174da8e11
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/config.json
@@ -0,0 +1,127 @@
+{
+ "assertors": [
+ {
+ "transition": "ROTATION",
+ "assertions": {
+ "presubmit": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtEnd",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsInvisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsInvisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleWindowsShownMoreThanOneConsecutiveEntry"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.VisibleLayersShownMoreThanOneConsecutiveEntry"
+ }
+ ],
+ "postsubmit": [],
+ "flaky": []
+ }
+ },
+ {
+ "transition": "APP_LAUNCH",
+ "assertions": {
+ "presubmit": [
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtEnd",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/NavigationBar0"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.NonAppWindowIsVisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAlways",
+ "args": [
+ "/StatusBar"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.EntireScreenCoveredAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppLayerReplacesLauncher"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsVisibleAtStart",
+ "args": [
+ "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LayerIsInvisibleAtEnd",
+ "args": [
+ "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+ ]
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppLayerIsInvisibleAtStart"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppLayerIsVisibleAtEnd"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.AppWindowReplacesLauncherAsTopWindow"
+ },
+ {
+ "class": "com.android.server.wm.flicker.service.assertors.common.LauncherWindowMovesOutOfTop"
+ }
+ ],
+ "postsubmit": [],
+ "flaky": []
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerInvalidTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerInvalidTrace.winscope
new file mode 100644
index 000000000..645f8be65
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerInvalidTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..4bdb32228
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/rotation/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/assertors/rotation/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/assertors/rotation/WindowManagerTrace.winscope
new file mode 100644
index 000000000..f146dc266
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/assertors/rotation/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/layers_dump_with_display.pb b/libraries/flicker/test/assets/testdata/layers_dump_with_display.pb
new file mode 100644
index 000000000..430b3cc5f
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/layers_dump_with_display.pb
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/layers_trace_close_app_with_rotation.winscope b/libraries/flicker/test/assets/testdata/layers_trace_close_app_with_rotation.winscope
new file mode 100644
index 000000000..59964cc5a
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/layers_trace_close_app_with_rotation.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/layers_trace_occluded.pb b/libraries/flicker/test/assets/testdata/layers_trace_occluded.pb
new file mode 100644
index 000000000..0c1693c28
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/layers_trace_occluded.pb
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/layers_trace_openchrome.pb b/libraries/flicker/test/assets/testdata/layers_trace_openchrome.pb
new file mode 100644
index 000000000..ba58762e1
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/layers_trace_openchrome.pb
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/layers_trace_pip_wmshell.pb b/libraries/flicker/test/assets/testdata/layers_trace_pip_wmshell.pb
new file mode 100644
index 000000000..71e3f4719
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/layers_trace_pip_wmshell.pb
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..b20d68cf0
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/WindowManagerTrace.winscope
new file mode 100644
index 000000000..b83c2ab7b
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/backbutton/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..e62a755e3
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/WindowManagerTrace.winscope
new file mode 100644
index 000000000..26c1c8c22
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/rotated/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..1a43a0328
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/WindowManagerTrace.winscope
new file mode 100644
index 000000000..2cb81491c
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/swipeup/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..08d2743be
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/WindowManagerTrace.winscope
new file mode 100644
index 000000000..96d30a041
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/appclose/switchapp/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..1a1de6156
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/WindowManagerTrace.winscope
new file mode 100644
index 000000000..8fb920091
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/cold/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..587154313
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/WindowManagerTrace.winscope
new file mode 100644
index 000000000..3ff92c696
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/intent/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..ac15b8a23
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/WindowManagerTrace.winscope
new file mode 100644
index 000000000..7503f8031
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/warm/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..935c7d0f8
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/WindowManagerTrace.winscope
new file mode 100644
index 000000000..28d77555c
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/applaunch/withrot/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..6bccfc10d
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/WindowManagerTrace.winscope
new file mode 100644
index 000000000..16171b3d4
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/applaunchnogesture/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..00f2be7a5
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/WindowManagerTrace.winscope
new file mode 100644
index 000000000..31a914c42
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/bygesture/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..8322e0b46
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/WindowManagerTrace.winscope
new file mode 100644
index 000000000..3a38466cc
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/horizontal/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..aa5cb0b5e
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/WindowManagerTrace.winscope
new file mode 100644
index 000000000..d25769bd0
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/appear/stationary/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..123dae767
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/WindowManagerTrace.winscope
new file mode 100644
index 000000000..22d7826d0
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/close/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..130dad9b2
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/WindowManagerTrace.winscope
new file mode 100644
index 000000000..6f7639a92
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/bygesture/openandclose/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..a09d715de
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/WindowManagerTrace.winscope
new file mode 100644
index 000000000..4059964ca
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/close/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..3ba3f02a2
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/WindowManagerTrace.winscope
new file mode 100644
index 000000000..691fff885
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/ime/disappear/closeapp/openandclose/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..9f01cd229
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/WindowManagerTrace.winscope
new file mode 100644
index 000000000..8c75f7c5d
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/norotation/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..27ab2810c
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/WindowManagerTrace.winscope
new file mode 100644
index 000000000..061067240
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/rotation/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..6c919c782
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/WindowManagerTrace.winscope
new file mode 100644
index 000000000..2cf460645
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/splitscreen/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..6011ac891
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/WindowManagerTrace.winscope
new file mode 100644
index 000000000..3e97adee4
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/stationary/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..31e22dda0
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/WindowManagerTrace.winscope
new file mode 100644
index 000000000..296cd455a
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/enter/twice/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..8859bb867
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/WindowManagerTrace.winscope
new file mode 100644
index 000000000..e5b1e1116
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/dismissbutton/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..7b7c57154
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/WindowManagerTrace.winscope
new file mode 100644
index 000000000..353480866
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/exit/swipe/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..c833fb4a1
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/WindowManagerTrace.winscope
new file mode 100644
index 000000000..633acee27
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..c833fb4a1
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/WindowManagerTrace.winscope
new file mode 100644
index 000000000..633acee27
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/expand/expand/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..351dd17ed
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/WindowManagerTrace.winscope
new file mode 100644
index 000000000..55a179c18
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/expand/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..9d3bb506d
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/WindowManagerTrace.winscope
new file mode 100644
index 000000000..def491fa9
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/pip/resize/shrink/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..e89887808
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/WindowManagerTrace.winscope
new file mode 100644
index 000000000..ead45aba1
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/displays/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..36fc6633f
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/WindowManagerTrace.winscope
new file mode 100644
index 000000000..b5537ac8c
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/regular/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..388f4e831
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/WindowManagerTrace.winscope
new file mode 100644
index 000000000..e7c8c4cdb
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/seamless/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/SurfaceFlingerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/SurfaceFlingerTrace.winscope
new file mode 100644
index 000000000..bcca47ef6
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/SurfaceFlingerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/WindowManagerTrace.winscope b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/WindowManagerTrace.winscope
new file mode 100644
index 000000000..6c276fbe9
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/tagprocessors/rotation/verticaltohorizontal/WindowManagerTrace.winscope
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/wm_trace_launcher_visible_background.pb b/libraries/flicker/test/assets/testdata/wm_trace_launcher_visible_background.pb
new file mode 100755
index 000000000..82a84c41c
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/wm_trace_launcher_visible_background.pb
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/wm_trace_open_from_overview.pb b/libraries/flicker/test/assets/testdata/wm_trace_open_from_overview.pb
new file mode 100755
index 000000000..30eb3e60e
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/wm_trace_open_from_overview.pb
Binary files differ
diff --git a/libraries/flicker/test/assets/testdata/wm_trace_split_screen.pb b/libraries/flicker/test/assets/testdata/wm_trace_split_screen.pb
new file mode 100644
index 000000000..893bda988
--- /dev/null
+++ b/libraries/flicker/test/assets/testdata/wm_trace_split_screen.pb
Binary files differ
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.kt
index f82d6dfda..b398ae5cb 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.kt
@@ -21,6 +21,7 @@ import com.android.server.wm.flicker.assertions.FlickerSubject
import com.android.server.wm.flicker.traces.FlickerFailureStrategy
import com.android.server.wm.flicker.traces.FlickerSubjectException
import com.android.server.wm.traces.common.ITraceEntry
+import com.google.common.truth.Fact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.StandardSubjectBuilder
import com.google.common.truth.Subject
@@ -50,6 +51,47 @@ class AssertionsCheckerTest {
}
@Test
+ fun canCheckChangingAssertions_IgnoreOptionalStart() {
+ val checker = AssertionsChecker<SimpleEntrySubject>()
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData42") { it.isData42() }
+ checker.add("isData0") { it.isData0() }
+ checker.test(getTestEntries(42, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun canCheckChangingAssertions_IgnoreOptionalEnd() {
+ val checker = AssertionsChecker<SimpleEntrySubject>()
+ checker.add("isData42") { it.isData42() }
+ checker.add("isData0") { it.isData0() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.test(getTestEntries(42, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun canCheckChangingAssertions_IgnoreOptionalMiddle() {
+ val checker = AssertionsChecker<SimpleEntrySubject>()
+ checker.add("isData42") { it.isData42() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData0") { it.isData0() }
+ checker.test(getTestEntries(42, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun canCheckChangingAssertions_IgnoreOptionalMultiple() {
+ val checker = AssertionsChecker<SimpleEntrySubject>()
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData42") { it.isData42() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData0") { it.isData0() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.add("isData1", isOptional = true) { it.isData1() }
+ checker.test(getTestEntries(42, 0, 0, 0, 0))
+ }
+
+ @Test
fun canCheckChangingAssertions_withNoAssertions() {
val checker = AssertionsChecker<SimpleEntrySubject>()
checker.test(getTestEntries(42, 0, 0, 0, 0))
@@ -98,7 +140,7 @@ class AssertionsCheckerTest {
require(failure is FlickerSubjectException) { "Unknown failure $failure" }
assertFailure(failure.cause)
.hasMessageThat()
- .contains("Assertion never became false: isData42")
+ .contains("Assertion never failed: isData42")
}
}
@@ -121,7 +163,9 @@ class AssertionsCheckerTest {
failureMetadata: FailureMetadata,
private val entry: SimpleEntry
) : FlickerSubject(failureMetadata, entry) {
- override val defaultFacts: String = "SimpleEntry(${entry.mData})"
+ override val timestamp: Long get() = 0
+ override val parent: FlickerSubject? get() = null
+ override val selfFacts = listOf(Fact.fact("SimpleEntry", entry.mData.toString()))
override fun clone(): FlickerSubject {
return SimpleEntrySubject(fm, entry)
}
@@ -134,6 +178,10 @@ class AssertionsCheckerTest {
check("is0").that(entry.mData).isEqualTo(0)
}
+ fun isData1() = apply {
+ check("is1").that(entry.mData).isEqualTo(1)
+ }
+
companion object {
/**
* Boiler-plate Subject.Factory for LayersTraceSubject
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/CommonConstants.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/CommonConstants.kt
new file mode 100644
index 000000000..07bd72ffe
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/CommonConstants.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("CommonConstants")
+package com.android.server.wm.flicker
+
+import com.android.server.wm.traces.common.FlickerComponentName
+
+val CHROME_COMPONENT = FlickerComponentName("com.android.chrome",
+ "org.chromium.chrome.browser.firstrun.FirstRunActivity")
+val CHROME_SPLASH_SCREEN_COMPONENT = FlickerComponentName("", "Splash Screen com.android.chrome")
+val DOCKER_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider")
+val IMAGINARY_COMPONENT = FlickerComponentName("", "ImaginaryWindow")
+val IME_ACTIVITY_COMPONENT = FlickerComponentName("com.android.server.wm.flicker.testapp",
+ "com.android.server.wm.flicker.testapp.ImeActivity")
+val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+val PIP_DISMISS_COMPONENT = FlickerComponentName("", "pip-dismiss-overlay")
+
+val SIMPLE_APP_COMPONENT = FlickerComponentName("com.android.server.wm.flicker.testapp",
+ "com.android.server.wm.flicker.testapp.SimpleActivity")
+private const val SHELL_PKG_NAME = "com.android.wm.shell.flicker.testapp"
+val SHELL_SPLIT_SCREEN_PRIMARY_COMPONENT = FlickerComponentName(SHELL_PKG_NAME,
+ "$SHELL_PKG_NAME.SplitScreenActivity")
+val SHELL_SPLIT_SCREEN_SECONDARY_COMPONENT = FlickerComponentName(SHELL_PKG_NAME,
+ "$SHELL_PKG_NAME.SplitScreenSecondaryActivity")
+
+val SCREEN_DECOR_COMPONENT = FlickerComponentName("", "ScreenDecorOverlay")
+val WALLPAPER_COMPONENT = FlickerComponentName(
+ "", "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2")
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt
index a894632c6..e8891f401 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt
@@ -34,14 +34,14 @@ class EventLogSubjectTest {
FocusEvent(0, "WinB", FocusEvent.Focus.LOST, "test"),
FocusEvent(0, "test WinC", FocusEvent.Focus.GAINED, "test"))
val result = builder.buildEventLogResult().eventLogSubject
- require(result != null) { "Event log subject was not built" }
- result.focusChanges(arrayOf("WinA", "WinB", "WinC"))
+ requireNotNull(result) { "Event log subject was not built" }
+ result.focusChanges("WinA", "WinB", "WinC")
.forAllEntries()
- result.focusChanges(arrayOf("WinA", "WinB")).forAllEntries()
- result.focusChanges(arrayOf("WinB", "WinC")).forAllEntries()
- result.focusChanges(arrayOf("WinA")).forAllEntries()
- result.focusChanges(arrayOf("WinB")).forAllEntries()
- result.focusChanges(arrayOf("WinC")).forAllEntries()
+ result.focusChanges("WinA", "WinB").forAllEntries()
+ result.focusChanges("WinB", "WinC").forAllEntries()
+ result.focusChanges("WinA").forAllEntries()
+ result.focusChanges("WinB").forAllEntries()
+ result.focusChanges("WinC").forAllEntries()
}
@Test
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerDSLTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerDSLTest.kt
index dd8d15e79..ba4bb6239 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerDSLTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerDSLTest.kt
@@ -228,4 +228,31 @@ class FlickerDSLTest {
.contains(exceptionMessage)
}
}
+
+ private val failedAssertion = AssertionData(tag = AssertionTag.END,
+ expectedSubjectClass = LayerTraceEntrySubject::class) {
+ this.fail("Expected exception")
+ }
+
+ @Test
+ fun exceptionContainsDebugInfo() {
+ val builder = FlickerBuilder(instrumentation)
+ builder.transitions { device.pressHome() }
+ val flicker = builder.build()
+ flicker.execute()
+
+ val error = assertThrows(AssertionError::class.java) {
+ flicker.checkAssertion(failedAssertion)
+ }
+ // Exception message
+ Truth.assertThat(error).hasMessageThat().contains("Expected exception")
+ // Subject facts
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace files")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains("Location")
+ // Correct stack trace point
+ Truth.assertThat(error).hasMessageThat().contains("failedAssertion")
+ }
}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceTest.kt
deleted file mode 100644
index 3016a0a09..000000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceTest.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker
-
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.google.common.truth.Truth
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [LayersTrace] tests. To run this test: `atest
- * FlickerLibTest:LayersTraceTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayersTraceTest {
- private fun detectRootLayer(fileName: String) {
- val layersTrace = readLayerTraceFromFile(fileName)
- for (entry in layersTrace.entries) {
- val rootLayers = entry.rootLayers
- Truth.assertWithMessage("Does not have any root layer")
- .that(rootLayers.size)
- .isGreaterThan(0)
- val firstParentId = rootLayers.first().parentId
- Truth.assertWithMessage("Has multiple root layers")
- .that(rootLayers.all { it.parentId == firstParentId })
- .isTrue()
- }
- }
-
- @Test
- fun testCanDetectRootLayer() {
- detectRootLayer("layers_trace_root.pb")
- }
-
- @Test
- fun testCanDetectRootLayerAOSP() {
- detectRootLayer("layers_trace_root_aosp.pb")
- }
-
- @Test
- fun testCanParseTraceWithoutHWC() {
- val layersTrace = readLayerTraceFromFile("layers_trace_no_hwc_composition.pb")
- layersTrace.forEach { entry ->
- Truth.assertWithMessage("Should have visible layers in all trace entries")
- .that(entry.visibleLayers).asList()
- .isNotEmpty()
- }
- }
-} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/UiDeviceExtensionsTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/UiDeviceExtensionsTest.kt
index 88141137e..9161a5dd9 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/UiDeviceExtensionsTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/UiDeviceExtensionsTest.kt
@@ -17,7 +17,9 @@
package com.android.server.wm.flicker
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.traces.parser.DeviceStateDump
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.parser.FLAG_STATE_DUMP_FLAG_LAYERS
import com.android.server.wm.traces.parser.FLAG_STATE_DUMP_FLAG_WM
import com.android.server.wm.traces.parser.WmStateDumpFlags
@@ -29,7 +31,7 @@ import org.junit.Test
import org.junit.runners.MethodSorters
/**
- * Contains [UiDeviceExtensions] tests.
+ * Contains [com.android.server.wm.traces.parser.Extensions] tests.
*
* To run this test: `atest FlickerLibTest:UiDeviceExtensionsTest`
*/
@@ -44,7 +46,7 @@ class UiDeviceExtensionsTest {
private fun getCurrStateDump(
@WmStateDumpFlags dumpFlags: Int = FLAG_STATE_DUMP_FLAG_WM.or(FLAG_STATE_DUMP_FLAG_LAYERS)
- ): DeviceStateDump {
+ ): DeviceStateDump<WindowManagerState?, LayerTraceEntry?> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
return getCurrentStateDump(instrumentation.uiAutomation, dumpFlags)
}
@@ -62,8 +64,8 @@ class UiDeviceExtensionsTest {
Truth.assertThat(currStateDump.first).isNotEmpty()
Truth.assertThat(currStateDump.second).isEmpty()
val currState = this.getCurrStateDump(FLAG_STATE_DUMP_FLAG_WM)
- Truth.assertThat(currState.wmTrace).isNotNull()
- Truth.assertThat(currState.layersTrace).isNull()
+ Truth.assertThat(currState.wmState).isNotNull()
+ Truth.assertThat(currState.layerState).isNull()
}
@Test
@@ -72,16 +74,22 @@ class UiDeviceExtensionsTest {
Truth.assertThat(currStateDump.first).isEmpty()
Truth.assertThat(currStateDump.second).isNotEmpty()
val currState = this.getCurrStateDump(FLAG_STATE_DUMP_FLAG_LAYERS)
- Truth.assertThat(currState.wmTrace).isNull()
- Truth.assertThat(currState.layersTrace).isNotNull()
+ Truth.assertThat(currState.wmState).isNull()
+ Truth.assertThat(currState.layerState).isNotNull()
}
@Test
fun canParseCurrentDeviceState() {
val currState = this.getCurrStateDump()
- Truth.assertThat(currState.wmTrace?.entries).hasSize(1)
- Truth.assertThat(currState.wmTrace?.entries?.first()?.windowStates).isNotEmpty()
- Truth.assertThat(currState.layersTrace?.entries).hasSize(1)
- Truth.assertThat(currState.layersTrace?.entries?.first()?.flattenedLayers).isNotEmpty()
+ Truth.assertThat(currState.wmState?.asTrace()?.entries ?: emptyArray())
+ .asList()
+ .hasSize(1)
+ Truth.assertThat(currState.wmState?.asTrace()?.entries?.first()?.windowStates)
+ .isNotEmpty()
+ Truth.assertThat(currState.layerState?.asTrace()?.entries ?: emptyArray())
+ .asList()
+ .hasSize(1)
+ Truth.assertThat(currState.layerState?.asTrace()?.entries?.first()?.flattenedLayers)
+ .isNotEmpty()
}
} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt
index bc376b205..4d2869fb5 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt
@@ -20,8 +20,10 @@ import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.traces.FlickerSubjectException
import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.common.tags.TagTrace
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
import com.android.server.wm.traces.parser.layers.LayersTraceParser
+import com.android.server.wm.traces.parser.tags.TagTraceParserUtil
import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
import com.google.common.io.ByteStreams
import com.google.common.truth.ExpectFailure
@@ -57,6 +59,15 @@ internal fun readLayerTraceFromFile(
}
}
+internal fun readTagTraceFromFile(relativePath: String): TagTrace {
+ return try {
+ TagTraceParserUtil.parseFromTrace(readTestFile(relativePath),
+ source = Paths.get(relativePath))
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+}
+
@Throws(Exception::class)
internal fun readTestFile(relativePath: String): ByteArray {
val context: Context = InstrumentationRegistry.getInstrumentation().context
@@ -95,4 +106,4 @@ fun assertFailure(failure: Throwable?): TruthFailureSubject {
}
require(target is AssertionError) { "Unknown failure $target" }
return ExpectFailure.assertThat(target)
-} \ No newline at end of file
+}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateSubjectTest.kt
deleted file mode 100644
index 959232a3c..000000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateSubjectTest.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker
-
-import android.graphics.Region
-import com.android.server.wm.flicker.traces.FlickerSubjectException
-import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
-import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject.Companion.assertThat
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-import java.lang.AssertionError
-
-/**
- * Contains [WindowManagerStateSubject] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerStateSubjectTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerStateSubjectTest {
- private val trace: WindowManagerTrace by lazy { readWmTraceFromFile("wm_trace_openchrome.pb") }
-
- @Test
- fun canDetectAboveAppWindowVisibility_isVisible() {
- assertThat(trace)
- .entry(9213763541297)
- .isAboveAppWindow("NavigationBar")
- .isAboveAppWindow("ScreenDecorOverlay")
- .isAboveAppWindow("StatusBar")
- }
-
- @Test
- fun canDetectAboveAppWindowVisibility_isInvisible() {
- val subject = assertThat(trace).entry(9213763541297)
- var failure = assertThrows(AssertionError::class.java) {
- subject.isAboveAppWindow("pip-dismiss-overlay")
- }
- assertFailure(failure).factValue("Is Invisible").contains("pip-dismiss-overlay")
-
- failure = assertThrows(AssertionError::class.java) {
- subject.isAboveAppWindow("NavigationBar", isVisible = false)
- }
- assertFailure(failure).factValue("Is Visible").contains("NavigationBar")
- }
-
- @Test
- fun canDetectWindowCoversAtLeastRegion_exactSize() {
- val entry = assertThat(trace)
- .entry(9213763541297)
-
- entry.frameRegion("StatusBar").coversAtLeast(Region(0, 0, 1440, 171))
- entry.frameRegion("com.google.android.apps.nexuslauncher")
- .coversAtLeast(Region(0, 0, 1440, 2960))
- }
-
- @Test
- fun canDetectWindowCoversAtLeastRegion_smallerRegion() {
- val entry = assertThat(trace)
- .entry(9213763541297)
- entry.frameRegion("StatusBar").coversAtLeast(Region(0, 0, 100, 100))
- entry.frameRegion("com.google.android.apps.nexuslauncher")
- .coversAtLeast(Region(0, 0, 100, 100))
- }
-
- @Test
- fun canDetectWindowCoversAtLeastRegion_largerRegion() {
- val subject = assertThat(trace).entry(9213763541297)
- var failure = assertThrows(FlickerSubjectException::class.java) {
- subject.frameRegion("StatusBar").coversAtLeast(Region(0, 0, 1441, 171))
- }
- assertFailure(failure).factValue("Uncovered region").contains("SkRegion((1440,0,1441,171))")
-
- failure = assertThrows(FlickerSubjectException::class.java) {
- subject.frameRegion("com.google.android.apps.nexuslauncher")
- .coversAtLeast(Region(0, 0, 1440, 2961))
- }
- assertFailure(failure).factValue("Uncovered region")
- .contains("SkRegion((0,2960,1440,2961))")
- }
-
- @Test
- fun canDetectWindowCoversAtMostRegion_extactSize() {
- val entry = assertThat(trace)
- .entry(9213763541297)
- entry.frameRegion("StatusBar").coversAtMost(Region(0, 0, 1440, 171))
- entry.frameRegion("com.google.android.apps.nexuslauncher")
- .coversAtMost(Region(0, 0, 1440, 2960))
- }
-
- @Test
- fun canDetectWindowCoversAtMostRegion_smallerRegion() {
- val subject = assertThat(trace).entry(9213763541297)
- var failure = assertThrows(FlickerSubjectException::class.java) {
- subject.frameRegion("StatusBar").coversAtMost(Region(0, 0, 100, 100))
- }
- assertFailure(failure).factValue("Out-of-bounds region")
- .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
-
- failure = assertThrows(FlickerSubjectException::class.java) {
- subject.frameRegion("com.google.android.apps.nexuslauncher")
- .coversAtMost(Region(0, 0, 100, 100))
- }
- assertFailure(failure).factValue("Out-of-bounds region")
- .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
- }
-
- @Test
- fun canDetectWindowCoversAtMostRegion_largerRegion() {
- val entry = assertThat(trace)
- .entry(9213763541297)
-
- entry.frameRegion("StatusBar").coversAtMost(Region(0, 0, 1441, 171))
- entry.frameRegion("com.google.android.apps.nexuslauncher")
- .coversAtMost(Region(0, 0, 1440, 2961))
- }
-
- @Test
- fun canDetectBelowAppWindowVisibility() {
- assertThat(trace)
- .entry(9213763541297)
- .containsNonAppWindow("wallpaper")
- }
-
- @Test
- fun canDetectAppWindowVisibility() {
- assertThat(trace)
- .entry(9213763541297)
- .containsAppWindow("com.google.android.apps.nexuslauncher")
-
- assertThat(trace)
- .entry(9215551505798)
- .containsAppWindow("com.android.chrome")
- }
-
- @Test
- fun canFailWithReasonForVisibilityChecks_windowNotFound() {
- val failure = assertThrows(FlickerSubjectException::class.java) {
- assertThat(trace)
- .entry(9213763541297)
- .containsNonAppWindow("ImaginaryWindow")
- }
- assertFailure(failure).factValue("Could not find")
- .contains("ImaginaryWindow")
- }
-
- @Test
- fun canFailWithReasonForVisibilityChecks_windowNotVisible() {
- val failure = assertThrows(FlickerSubjectException::class.java) {
- assertThat(trace)
- .entry(9213763541297)
- .containsNonAppWindow("InputMethod")
- }
- assertFailure(failure).factValue("Is Invisible")
- .contains("InputMethod")
- }
-
- @Test
- fun canDetectAppZOrder() {
- assertThat(trace)
- .entry(9215551505798)
- .containsAppWindow("com.google.android.apps.nexuslauncher", isVisible = true)
- .showsAppWindowOnTop("com.android.chrome")
- }
-
- @Test
- fun canFailWithReasonForZOrderChecks_windowNotOnTop() {
- val failure = assertThrows(FlickerSubjectException::class.java) {
- assertThat(trace)
- .entry(9215551505798)
- .showsAppWindowOnTop("com.google.android.apps.nexuslauncher")
- }
- assertFailure(failure)
- .factValue("Found")
- .contains("Splash Screen com.android.chrome")
- }
-} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceSubjectTest.kt
deleted file mode 100644
index d44c8bacb..000000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceSubjectTest.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker
-
-import com.android.server.wm.flicker.traces.FlickerSubjectException
-import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
-import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject.Companion.assertThat
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [WindowManagerTraceSubject] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerTraceSubjectTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerTraceSubjectTest {
- private val chromeTrace by lazy { readWmTraceFromFile("wm_trace_openchrome.pb") }
- private val imeTrace by lazy { readWmTraceFromFile("wm_trace_ime.pb") }
-
- @Test
- fun testVisibleAppWindowForRange() {
- assertThat(chromeTrace)
- .showsAppWindowOnTop("NexusLauncherActivity")
- .showsAboveAppWindow("ScreenDecorOverlay")
- .forRange(9213763541297L, 9215536878453L)
- assertThat(chromeTrace)
- .showsAppWindowOnTop("com.android.chrome")
- .showsAppWindow("NexusLauncherActivity")
- .showsAboveAppWindow("ScreenDecorOverlay")
- .then()
- .showsAppWindowOnTop("com.android.chrome")
- .hidesAppWindow("NexusLauncherActivity")
- .showsAboveAppWindow("ScreenDecorOverlay")
- .forRange(9215551505798L, 9216093628925L)
- }
-
- @Test
- fun testCanTransitionInAppWindow() {
- assertThat(chromeTrace)
- .showsAppWindowOnTop("NexusLauncherActivity")
- .showsAboveAppWindow("ScreenDecorOverlay")
- .then()
- .showsAppWindowOnTop("com.android.chrome")
- .showsAboveAppWindow("ScreenDecorOverlay")
- .forAllEntries()
- }
-
- @Test
- fun testCanInspectBeginning() {
- assertThat(chromeTrace)
- .first()
- .showsAppWindowOnTop("NexusLauncherActivity")
- .isAboveAppWindow("ScreenDecorOverlay")
- }
-
- @Test
- fun testCanInspectAppWindowOnTop() {
- assertThat(chromeTrace)
- .first()
- .showsAppWindowOnTop("NexusLauncherActivity", "InvalidWindow")
-
- val failure = assertThrows(FlickerSubjectException::class.java) {
- assertThat(chromeTrace)
- .first()
- .showsAppWindowOnTop("AnotherInvalidWindow", "InvalidWindow")
- .fail("Could not detect the top app window")
- }
- assertFailure(failure).factValue("Could not find").contains("InvalidWindow")
- }
-
- @Test
- fun testCanInspectEnd() {
- assertThat(chromeTrace)
- .last()
- .showsAppWindowOnTop("com.android.chrome")
- .isAboveAppWindow("ScreenDecorOverlay")
- }
-
- @Test
- fun testCanTransitionNonAppWindow() {
- assertThat(imeTrace)
- .skipUntilFirstAssertion()
- .hidesNonAppWindow("InputMethod")
- .then()
- .showsNonAppWindow("InputMethod")
- .forAllEntries()
- }
-
- @Test(expected = AssertionError::class)
- fun testCanDetectOverlappingWindows() {
- assertThat(imeTrace)
- .noWindowsOverlap("InputMethod", "NavigationBar", "ImeActivity")
- .forAllEntries()
- }
-
- @Test
- fun testCanTransitionAboveAppWindow() {
- assertThat(imeTrace)
- .skipUntilFirstAssertion()
- .hidesAboveAppWindow("InputMethod")
- .then()
- .showsAboveAppWindow("InputMethod")
- .forAllEntries()
- }
-
- @Test
- fun testCanTransitionBelowAppWindow() {
- val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb")
- assertThat(trace)
- .skipUntilFirstAssertion()
- .showsBelowAppWindow("Wallpaper")
- .then()
- .hidesBelowAppWindow("Wallpaper")
- .forAllEntries()
- }
-
- @Test
- fun testCanDetectVisibleWindowsMoreThanOneConsecutiveEntry() {
- val trace = readWmTraceFromFile("wm_trace_valid_visible_windows.pb")
- assertThat(trace).visibleWindowsShownMoreThanOneConsecutiveEntry().forAllEntries()
- }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/LayerSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerSubjectTest.kt
index cb99b650f..5fff30272 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/LayerSubjectTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerSubjectTest.kt
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker
+package com.android.server.wm.flicker.layers
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.traces.layers.LayerSubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
-import com.android.server.wm.traces.common.Bounds
+import com.android.server.wm.traces.common.Size
import com.google.common.truth.Truth
import org.junit.FixMethodOrder
import org.junit.Test
@@ -30,7 +33,7 @@ import org.junit.runners.MethodSorters
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class LayerSubjectTest {
@Test
- fun exceptionContainsDebugInfo() {
+ fun exceptionContainsDebugInfoImaginary() {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb")
val error = assertThrows(AssertionError::class.java) {
assertThat(layersTraceEntries)
@@ -38,11 +41,33 @@ class LayerSubjectTest {
.layer("ImaginaryLayer", 0)
.exists()
}
- Truth.assertThat(error).hasMessageThat().contains("Trace:")
- Truth.assertThat(error).hasMessageThat().contains("Path: ")
- Truth.assertThat(error).hasMessageThat().contains("Entry:")
- Truth.assertThat(error).hasMessageThat().contains("Frame:")
- Truth.assertThat(error).hasMessageThat().contains("Layer:")
+ Truth.assertThat(error).hasMessageThat().contains("ImaginaryLayer")
+ Truth.assertThat(error).hasMessageThat().contains("What?")
+ Truth.assertThat(error).hasMessageThat().contains("Where?")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ Truth.assertThat(error).hasMessageThat().contains("Layer name")
+ }
+
+ @Test
+ fun exceptionContainsDebugInfoConcrete() {
+ val layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb")
+ val error = assertThrows(AssertionError::class.java) {
+ assertThat(layersTraceEntries)
+ .first()
+ .subjects
+ .first()
+ .doesNotExist()
+ }
+ Truth.assertThat(error).hasMessageThat().contains("What?")
+ Truth.assertThat(error).hasMessageThat().contains("Where?")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
}
@Test
@@ -50,7 +75,7 @@ class LayerSubjectTest {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb")
assertThat(layersTraceEntries)
.layer("SoundVizWallpaperV2", 26033)
- .hasBufferSize(Bounds(1440, 2960))
+ .hasBufferSize(Size(1440, 2960))
.hasScalingMode(0)
assertThat(layersTraceEntries)
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/LayerTraceEntrySubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntrySubjectTest.kt
index 76375b808..b8fe45a96 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/LayerTraceEntrySubjectTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntrySubjectTest.kt
@@ -14,11 +14,20 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker
+package com.android.server.wm.flicker.layers
import android.graphics.Region
+import com.android.server.wm.flicker.DOCKER_STACK_DIVIDER_COMPONENT
+import com.android.server.wm.flicker.IMAGINARY_COMPONENT
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.SIMPLE_APP_COMPONENT
+import com.android.server.wm.flicker.assertFailure
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.assertions.FlickerSubject
+import com.android.server.wm.flicker.readLayerTraceFromFile
import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.traces.common.FlickerComponentName
import com.google.common.truth.Truth
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,28 +45,31 @@ class LayerTraceEntrySubjectTest {
val error = assertThrows(AssertionError::class.java) {
LayersTraceSubject.assertThat(layersTraceEntries)
.first()
- .contains("ImaginaryLayer")
+ .visibleRegion(IMAGINARY_COMPONENT)
}
- Truth.assertThat(error).hasMessageThat().contains("Trace:")
- Truth.assertThat(error).hasMessageThat().contains("Path: ")
- Truth.assertThat(error).hasMessageThat().contains("Entry:")
+ Truth.assertThat(error).hasMessageThat().contains(IMAGINARY_COMPONENT.className)
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains(FlickerSubject.ASSERTION_TAG)
}
@Test
fun testCanInspectBeginning() {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
LayerTraceEntrySubject.assertThat(layersTraceEntries.entries.first())
- .isVisible("NavigationBar0#0")
- .notContains("DockedStackDivider#0")
- .isVisible("NexusLauncherActivity#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .notContains(DOCKER_STACK_DIVIDER_COMPONENT)
+ .isVisible(LAUNCHER_COMPONENT)
}
@Test
fun testCanInspectEnd() {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
LayerTraceEntrySubject.assertThat(layersTraceEntries.entries.last())
- .isVisible("NavigationBar0#0")
- .isVisible("DockedStackDivider#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .isVisible(DOCKER_STACK_DIVIDER_COMPONENT)
}
// b/75276931
@@ -82,17 +94,16 @@ class LayerTraceEntrySubjectTest {
// Visible region tests
@Test
fun canTestLayerVisibleRegion_layerDoesNotExist() {
- val imaginaryLayer = "ImaginaryLayer"
val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb")
val expectedVisibleRegion = Region(0, 0, 1, 1)
val error = assertThrows(AssertionError::class.java) {
LayersTraceSubject.assertThat(trace).entry(937229257165)
- .visibleRegion(imaginaryLayer)
+ .visibleRegion(IMAGINARY_COMPONENT)
.coversExactly(expectedVisibleRegion)
}
assertFailure(error)
.factValue("Could not find")
- .isEqualTo(imaginaryLayer)
+ .contains(IMAGINARY_COMPONENT.toWindowName())
}
@Test
@@ -101,7 +112,7 @@ class LayerTraceEntrySubjectTest {
val expectedVisibleRegion = Region(0, 0, 1, 1)
val error = assertThrows(AssertionError::class.java) {
LayersTraceSubject.assertThat(trace).entry(937126074082)
- .visibleRegion("DockedStackDivider#0")
+ .visibleRegion(DOCKER_STACK_DIVIDER_COMPONENT)
.coversExactly(expectedVisibleRegion)
}
assertFailure(error)
@@ -115,7 +126,7 @@ class LayerTraceEntrySubjectTest {
val expectedVisibleRegion = Region(0, 0, 1, 1)
val error = assertThrows(AssertionError::class.java) {
LayersTraceSubject.assertThat(trace).entry(935346112030)
- .visibleRegion("SimpleActivity#0")
+ .visibleRegion(SIMPLE_APP_COMPONENT)
.coversExactly(expectedVisibleRegion)
}
assertFailure(error)
@@ -129,7 +140,7 @@ class LayerTraceEntrySubjectTest {
val expectedVisibleRegion = Region(0, 0, 1440, 99)
val error = assertThrows(AssertionError::class.java) {
LayersTraceSubject.assertThat(trace).entry(937126074082)
- .visibleRegion("StatusBar")
+ .visibleRegion(FlickerComponentName.STATUS_BAR)
.coversExactly(expectedVisibleRegion)
}
assertFailure(error)
@@ -142,7 +153,7 @@ class LayerTraceEntrySubjectTest {
val trace = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
val expectedVisibleRegion = Region(0, 0, 1080, 145)
LayersTraceSubject.assertThat(trace).entry(90480846872160)
- .visibleRegion("StatusBar")
+ .visibleRegion(FlickerComponentName.STATUS_BAR)
.coversExactly(expectedVisibleRegion)
}
@@ -151,7 +162,7 @@ class LayerTraceEntrySubjectTest {
val trace = readLayerTraceFromFile("layers_trace_invalid_layer_visibility.pb")
val error = assertThrows(AssertionError::class.java) {
LayersTraceSubject.assertThat(trace).entry(252794268378458)
- .isVisible("com.android.server.wm.flicker.testapp")
+ .isVisible(SIMPLE_APP_COMPONENT)
}
assertFailure(error)
.factValue("Is Invisible")
@@ -167,7 +178,8 @@ class LayerTraceEntrySubjectTest {
entry.visibleRegion(useCompositionEngineRegionOnly = false)
.coversExactly(Region(0, 0, 1440, 2960))
- entry.visibleRegion("InputMethod#0", useCompositionEngineRegionOnly = false)
+ entry.visibleRegion(FlickerComponentName.IME,
+ useCompositionEngineRegionOnly = false)
.coversExactly(Region(0, 171, 1440, 2960))
}
} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceEntryTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceEntryTest.kt
index 616fc47ee..670bba445 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceEntryTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceEntryTest.kt
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker
+package com.android.server.wm.flicker.layers
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
+import com.android.server.wm.flicker.IMAGINARY_COMPONENT
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.readLayerTraceFromFile
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
import com.google.common.truth.Truth
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runners.MethodSorters
-import kotlin.AssertionError
/**
* Contains [LayerTraceEntry] tests. To run this test: `atest
@@ -36,11 +38,12 @@ class LayersTraceEntryTest {
val error = assertThrows(AssertionError::class.java) {
assertThat(layersTraceEntries)
.first()
- .contains("ImaginaryLayer")
+ .contains(IMAGINARY_COMPONENT)
}
- Truth.assertThat(error).hasMessageThat().contains("Trace:")
- Truth.assertThat(error).hasMessageThat().contains("Path: ")
- Truth.assertThat(error).hasMessageThat().contains("Entry:")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace end")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
}
@Test
@@ -107,8 +110,8 @@ class LayersTraceEntryTest {
Truth.assertThat(trace.entries.first().timestamp).isEqualTo(922839428857)
Truth.assertThat(trace.entries.last().timestamp).isEqualTo(941432656959)
Truth.assertThat(trace.entries.first().flattenedLayers).asList().hasSize(57)
- val layers = trace.entries.first().rootLayers
- Truth.assertThat(layers[0].children).hasSize(3)
+ val layers = trace.entries.first().children
+ Truth.assertThat(layers[0].children).asList().hasSize(3)
Truth.assertThat(layers[1].children).isEmpty()
}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceSubjectTest.kt
index d47820efa..9ef6a0325 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceSubjectTest.kt
@@ -14,13 +14,21 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker
+package com.android.server.wm.flicker.layers
-import android.graphics.Region
import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.DOCKER_STACK_DIVIDER_COMPONENT
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.SIMPLE_APP_COMPONENT
+import com.android.server.wm.flicker.assertFailure
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.readLayerTraceFromFile
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.Region
import com.android.server.wm.traces.common.layers.LayersTrace
+import com.android.server.wm.traces.parser.minus
import com.google.common.truth.Truth
import org.junit.FixMethodOrder
import org.junit.Test
@@ -39,10 +47,9 @@ class LayersTraceSubjectTest {
assertThat(layersTraceEntries)
.isEmpty()
}
- Truth.assertThat(error).hasMessageThat().contains("Trace:")
- Truth.assertThat(error).hasMessageThat().contains("Path: ")
- Truth.assertThat(error).hasMessageThat().contains("Start:")
- Truth.assertThat(error).hasMessageThat().contains("End:")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace end")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
}
@Test
@@ -64,9 +71,9 @@ class LayersTraceSubjectTest {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
assertThat(layersTraceEntries)
.first()
- .isVisible("NavigationBar0#0")
- .notContains("DockedStackDivider#0")
- .isVisible("NexusLauncherActivity#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .notContains(DOCKER_STACK_DIVIDER_COMPONENT)
+ .isVisible(LAUNCHER_COMPONENT)
}
@Test
@@ -74,22 +81,37 @@ class LayersTraceSubjectTest {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
assertThat(layersTraceEntries)
.last()
- .isVisible("NavigationBar0#0")
- .isVisible("DockedStackDivider#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .isVisible(DOCKER_STACK_DIVIDER_COMPONENT)
+ }
+
+ @Test
+ fun testAssertionsOnRange() {
+ val layersTraceEntries = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
+
+ assertThat(layersTraceEntries)
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .isInvisible(DOCKER_STACK_DIVIDER_COMPONENT)
+ .forRange(90480846872160L, 90480994138424L)
+
+ assertThat(layersTraceEntries)
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .isVisible(DOCKER_STACK_DIVIDER_COMPONENT)
+ .forRange(90491795074136L, 90493757372977L)
}
@Test
fun testCanDetectChangingAssertions() {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_launch_split_screen.pb")
assertThat(layersTraceEntries)
- .isVisible("NavigationBar0#0")
- .notContains("DockedStackDivider#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .notContains(DOCKER_STACK_DIVIDER_COMPONENT)
.then()
- .isVisible("NavigationBar0#0")
- .isInvisible("DockedStackDivider#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .isInvisible(DOCKER_STACK_DIVIDER_COMPONENT)
.then()
- .isVisible("NavigationBar0#0")
- .isVisible("DockedStackDivider#0")
+ .isVisible(FlickerComponentName.NAV_BAR)
+ .isVisible(DOCKER_STACK_DIVIDER_COMPONENT)
.forAllEntries()
}
@@ -99,9 +121,9 @@ class LayersTraceSubjectTest {
val layersTraceEntries = readLayerTraceFromFile("layers_trace_invalid_layer_visibility.pb")
val error = assertThrows(AssertionError::class.java) {
assertThat(layersTraceEntries)
- .isVisible("com.android.server.wm.flicker.testapp")
+ .isVisible(SIMPLE_APP_COMPONENT)
.then()
- .isInvisible("com.android.server.wm.flicker.testapp")
+ .isInvisible(SIMPLE_APP_COMPONENT)
.forAllEntries()
}
@@ -156,7 +178,8 @@ class LayersTraceSubjectTest {
val layersTraceEntries = readLayerTraceFromFile(
"layers_trace_invalid_visible_layers.pb")
assertThat(layersTraceEntries)
- .visibleLayersShownMoreThanOneConsecutiveEntry(listOf("StatusBar#0"))
+ .visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(FlickerComponentName.STATUS_BAR))
.forAllEntries()
}
@@ -164,15 +187,17 @@ class LayersTraceSubjectTest {
fun testCanIgnoreLayerShorterNameInVisibleLayersMoreThanOneConsecutiveEntry() {
val layersTraceEntries = readLayerTraceFromFile(
"one_visible_layer_launcher_trace.pb")
+ val launcherComponent = FlickerComponentName("com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity#1")
assertThat(layersTraceEntries)
- .visibleLayersShownMoreThanOneConsecutiveEntry(listOf("Launcher"))
+ .visibleLayersShownMoreThanOneConsecutiveEntry(listOf(launcherComponent))
.forAllEntries()
}
private fun detectRootLayer(fileName: String) {
val layersTrace = readLayerTraceFromFile(fileName)
for (entry in layersTrace.entries) {
- val rootLayers = entry.rootLayers
+ val rootLayers = entry.children
Truth.assertWithMessage("Does not have any root layer")
.that(rootLayers.size)
.isGreaterThan(0)
@@ -193,7 +218,67 @@ class LayersTraceSubjectTest {
detectRootLayer("layers_trace_root_aosp.pb")
}
+ @Test
+ fun canTestLayerOccludedByAppLayerIsNotVisible() {
+ val trace = readLayerTraceFromFile("layers_trace_occluded.pb")
+ val entry = assertThat(trace).entry(1700382131522L)
+ entry.isVisible(SIMPLE_APP_COMPONENT)
+ }
+
+ @Test
+ fun testCanDetectLayerExpanding() {
+ val layersTraceEntries = readLayerTraceFromFile("layers_trace_openchrome.pb")
+ val animation = assertThat(layersTraceEntries).layers("animation-leash of app_transition#0")
+ // Obtain the area of each layer and checks if the next area is
+ // greater or equal to the previous one
+ val areas = animation.map {
+ val region = it.layer?.visibleRegion ?: Region()
+ val area = region.width * region.height
+ area
+ }
+ val expanding = areas.zipWithNext { currentArea, nextArea ->
+ nextArea >= currentArea
+ }
+
+ Truth.assertWithMessage("Animation leash should be expanding")
+ .that(expanding.all { it })
+ .isTrue()
+ }
+
+ @Test
+ fun checkVisibleRegionAppMinusPipLayer() {
+ val layersTraceEntries = readLayerTraceFromFile("layers_trace_pip_wmshell.pb")
+ val subject = assertThat(layersTraceEntries).last()
+
+ try {
+ subject.visibleRegion(FIXED_APP).coversExactly(DISPLAY_REGION_ROTATED)
+ error("Layer is partially covered by a Pip layer and should " +
+ "not cover the device screen")
+ } catch (e: AssertionError) {
+ val pipRegion = subject.visibleRegion(PIP_APP).region
+ val expectedWithoutPip = DISPLAY_REGION_ROTATED.minus(pipRegion)
+ subject.visibleRegion(FIXED_APP)
+ .coversExactly(expectedWithoutPip)
+ }
+ }
+
+ @Test
+ fun checkVisibleRegionAppPlusPipLayer() {
+ val layersTraceEntries = readLayerTraceFromFile("layers_trace_pip_wmshell.pb")
+ val subject = assertThat(layersTraceEntries).last()
+ val pipRegion = subject.visibleRegion(PIP_APP).region
+ subject.visibleRegion(FIXED_APP)
+ .plus(pipRegion)
+ .coversExactly(DISPLAY_REGION_ROTATED)
+ }
+
companion object {
- private val DISPLAY_REGION = Region(0, 0, 1440, 2880)
+ private val DISPLAY_REGION = android.graphics.Region(0, 0, 1440, 2880)
+ private val DISPLAY_REGION_ROTATED = Region(0, 0, 2160, 1080)
+ private const val SHELL_APP_PACKAGE = "com.android.wm.shell.flicker.testapp"
+ private val FIXED_APP = FlickerComponentName(SHELL_APP_PACKAGE,
+ "$SHELL_APP_PACKAGE.FixedActivity")
+ private val PIP_APP = FlickerComponentName(SHELL_APP_PACKAGE,
+ "$SHELL_APP_PACKAGE.PipActivity")
}
}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt
new file mode 100644
index 000000000..bac0ac45f
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.layers
+
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
+import com.android.server.wm.traces.common.layers.LayersTrace
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [LayersTrace] tests. To run this test: `atest
+ * FlickerLibTest:LayersTraceTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayersTraceTest {
+ private fun detectRootLayer(fileName: String) {
+ val layersTrace = readLayerTraceFromFile(fileName)
+ for (entry in layersTrace.entries) {
+ val rootLayers = entry.children
+ Truth.assertWithMessage("Does not have any root layer")
+ .that(rootLayers.size)
+ .isGreaterThan(0)
+ val firstParentId = rootLayers.first().parentId
+ Truth.assertWithMessage("Has multiple root layers")
+ .that(rootLayers.all { it.parentId == firstParentId })
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun testCanDetectRootLayer() {
+ detectRootLayer("layers_trace_root.pb")
+ }
+
+ @Test
+ fun testCanDetectRootLayerAOSP() {
+ detectRootLayer("layers_trace_root_aosp.pb")
+ }
+
+ @Test
+ fun testCanParseTraceWithoutHWC() {
+ val layersTrace = readLayerTraceFromFile("layers_trace_no_hwc_composition.pb")
+ layersTrace.forEach { entry ->
+ Truth.assertWithMessage("Should have visible layers in all trace entries")
+ .that(entry.visibleLayers).asList()
+ .isNotEmpty()
+ }
+ }
+
+ @Test
+ fun canParseFromDumpWithDisplay() {
+ val trace = readLayerTraceFromFile("layers_dump_with_display.pb")
+ Truth.assertWithMessage("Dump is not empty")
+ .that(trace)
+ .isNotEmpty()
+ Truth.assertWithMessage("Dump contains display is not empty")
+ .that(trace.first().displays)
+ .asList()
+ .isNotEmpty()
+ }
+
+ @Test
+ fun canTestLayerOccludedBy_appLayerHasVisibleRegion() {
+ val trace = readLayerTraceFromFile("layers_trace_occluded.pb")
+ val entry = trace.getEntry(1700382131522L)
+ val layer = entry.getLayerWithBuffer(
+ "com.android.server.wm.flicker.testapp.SimpleActivity#0")
+ Truth.assertWithMessage("App should be visible")
+ .that(layer?.visibleRegion?.isEmpty).isFalse()
+ Truth.assertWithMessage("App should visible region")
+ .that(layer?.visibleRegion?.toString())
+ .contains("(346, 1583) - (1094, 2839)")
+
+ val splashScreenLayer = entry.getLayerWithBuffer(
+ "Splash Screen com.android.server.wm.flicker.testapp.SimpleActivity#0")
+ Truth.assertWithMessage("Splash screen should be visible")
+ .that(layer?.visibleRegion?.isEmpty).isFalse()
+ Truth.assertWithMessage("Splash screen visible region")
+ .that(layer?.visibleRegion?.toString())
+ .contains("(346, 1583) - (1094, 2839)")
+ }
+
+ @Test
+ fun canTestLayerOccludedBy_appLayerIsOccludedBySplashScreen() {
+ val layerName = "com.android.server.wm.flicker.testapp.SimpleActivity#0"
+ val trace = readLayerTraceFromFile("layers_trace_occluded.pb")
+ val entry = trace.getEntry(1700382131522L)
+ val layer = entry.getLayerWithBuffer(layerName)
+ val occludedBy = layer?.occludedBy ?: emptyArray()
+ val partiallyOccludedBy = layer?.partiallyOccludedBy ?: emptyArray()
+ Truth.assertWithMessage("Layer $layerName should not be occluded")
+ .that(occludedBy).isEmpty()
+ Truth.assertWithMessage("Layer $layerName should be partially occluded")
+ .that(partiallyOccludedBy).isNotEmpty()
+ Truth.assertWithMessage("Layer $layerName should be partially occluded")
+ .that(partiallyOccludedBy.joinToString())
+ .contains("Splash Screen com.android.server.wm.flicker.testapp#0 buffer:w:1440, " +
+ "h:3040, stride:1472, format:1 frame#1 visible:(346, 1583) - (1094, 2839)")
+ }
+
+ @Test
+ fun exceptionContainsDebugInfo() {
+ val layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb")
+ val error = assertThrows(AssertionError::class.java) {
+ LayersTraceSubject.assertThat(layersTraceEntries)
+ .isEmpty()
+ }
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ }
+
+ @Test
+ fun canFilter() {
+ val trace = readLayerTraceFromFile("layers_trace_openchrome.pb")
+ val splitlayersTrace = trace.filter(71607477186189, 71607812120180)
+
+ Truth.assertThat(splitlayersTrace).isNotEmpty()
+
+ Truth.assertThat(splitlayersTrace.entries.first().timestamp).isEqualTo(71607477186189)
+ Truth.assertThat(splitlayersTrace.entries.last().timestamp).isEqualTo(71607812120180)
+ }
+
+ @Test
+ fun canFilter_wrongTimestamps() {
+ val trace = readLayerTraceFromFile("layers_trace_openchrome.pb")
+ val splitLayersTrace = trace.filter(9213763541297, 9215895891561)
+
+ Truth.assertThat(splitLayersTrace).isEmpty()
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt
index c8ac97dcf..37001beae 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt
@@ -63,7 +63,7 @@ class ScreenRecorderTest {
@Test
fun videoCanBeSaved() {
mScreenRecorder.start()
- SystemClock.sleep(100)
+ SystemClock.sleep(3000)
mScreenRecorder.stop()
val builder = FlickerRunResult.Builder()
mScreenRecorder.save("test", builder)
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt
index 39f70a5f2..fa7aee4ca 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt
@@ -20,7 +20,8 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.server.wm.flicker.FlickerRunResult
import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.traces.parser.DeviceStateDump
+import com.android.server.wm.traces.common.DeviceTraceDump
+import com.android.server.wm.traces.parser.DeviceDumpParser
import com.google.common.io.Files
import com.google.common.truth.Truth
import org.junit.After
@@ -81,19 +82,19 @@ abstract class TraceMonitorTest<T : TransitionMonitor> {
savedTrace = getTraceFile(result) ?: error("Could not find saved trace file")
val testFile = savedTrace.toFile()
Truth.assertThat(testFile.exists()).isTrue()
- val calculatedChecksum = TraceMonitor.calculateChecksum(savedTrace)
- Truth.assertThat(calculatedChecksum).isEqualTo(traceMonitor.checksum)
val trace = Files.toByteArray(testFile)
Truth.assertThat(trace.size).isGreaterThan(0)
assertTrace(trace)
}
- private fun validateTrace(dump: DeviceStateDump) {
+ private fun validateTrace(dump: DeviceTraceDump) {
Truth.assertWithMessage("Could not obtain SF trace")
- .that(dump.layersTrace?.entries)
+ .that(dump.layersTrace?.entries ?: emptyArray())
+ .asList()
.isNotEmpty()
Truth.assertWithMessage("Could not obtain WM trace")
- .that(dump.wmTrace?.entries)
+ .that(dump.wmTrace?.entries ?: emptyArray())
+ .asList()
.isNotEmpty()
}
@@ -114,7 +115,7 @@ abstract class TraceMonitorTest<T : TransitionMonitor> {
device.pressRecentApps()
}
- val dump = DeviceStateDump.fromTrace(trace.first, trace.second)
+ val dump = DeviceDumpParser.fromTrace(trace.first, trace.second)
this.validateTrace(dump)
}
}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/AssertionEngineTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/AssertionEngineTest.kt
new file mode 100644
index 000000000..99c08cec6
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/AssertionEngineTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readTagTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.tags.Transition
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [AssertionEngine] tests. To run this test:
+ * `atest FlickerLibTest:AssertionEngineTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AssertionEngineTest {
+ private val assertionEngine = AssertionEngine(emptyList()) { }
+ private val wmTrace by lazy {
+ readWmTraceFromFile("assertors/AppLaunchAndRotationsWindowManagerTrace.winscope")
+ }
+ private val layersTrace by lazy {
+ readLayerTraceFromFile("assertors/AppLaunchAndRotationsSurfaceFlingerTrace.winscope")
+ }
+ private val tagTrace by lazy {
+ readTagTraceFromFile("assertors/AppLaunchAndRotationsTagTrace.winscope")
+ }
+ private val transitionTags by lazy { assertionEngine.getTransitionTags(tagTrace) }
+
+ @Test
+ fun canExtractTransitionTags() {
+ Truth.assertThat(transitionTags).isNotEmpty()
+ Truth.assertThat(transitionTags.size).isEqualTo(3)
+ }
+
+ @Test
+ fun canSplitTraces_singleTag() {
+ val blocks = transitionTags
+ .filter { it.tag.transition == Transition.APP_LAUNCH }
+ .map { assertionEngine.splitTraces(it, wmTrace, layersTrace) }
+
+ Truth.assertThat(blocks).isNotEmpty()
+ Truth.assertThat(blocks.size).isEqualTo(1)
+
+ val entries = blocks.first().first.entries
+ Truth.assertThat(entries.first().timestamp).isEqualTo(294063112453765)
+ Truth.assertThat(entries.last().timestamp).isEqualTo(294063379330458)
+ }
+
+ @Test
+ fun canSplitLayersTrace_mergedTags() {
+ val blocks = transitionTags
+ .filter { it.tag.transition == Transition.ROTATION }
+ .map { assertionEngine.splitTraces(it, wmTrace, layersTrace) }
+
+ Truth.assertThat(blocks).isNotEmpty()
+ Truth.assertThat(blocks.size).isEqualTo(2)
+
+ val entries = blocks.last().second.entries
+ Truth.assertThat(entries.first().timestamp).isEqualTo(294064497020048)
+ Truth.assertThat(entries.last().timestamp).isEqualTo(294064981192909)
+ }
+
+ @Test
+ fun canSplitLayersTrace_noTags() {
+ val blocks = transitionTags
+ .filter { it.tag.transition == Transition.APP_CLOSE }
+ .map { assertionEngine.splitTraces(it, wmTrace, layersTrace) }
+
+ Truth.assertThat(blocks).isEmpty()
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/ErrorParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/ErrorParserTest.kt
new file mode 100644
index 000000000..44d4d6e2a
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/ErrorParserTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import com.android.server.wm.flicker.FlickerErrorProto
+import com.android.server.wm.flicker.FlickerErrorStateProto
+import com.android.server.wm.flicker.FlickerErrorTraceProto
+import com.android.server.wm.traces.parser.errors.ErrorTraceParserUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [ErrorTraceParserUtil] and [FlickerErrorProto] tests. To run this test: `atest
+ * FlickerLibTest:ErrorParserTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ErrorParserTest {
+ private val error = FlickerErrorProto.newBuilder()
+ .setLayerId(1)
+ .setTaskId(2)
+ .setWindowToken("token")
+ .setMessage("Error!")
+ .setStacktrace("stacktrace of error")
+ .setAssertionName("LayerIsVisibleAtStart")
+ .build()
+ private val state = FlickerErrorStateProto.newBuilder()
+ .setTimestamp(100)
+ .addErrors(error)
+ .build()
+ private val trace = FlickerErrorTraceProto.newBuilder()
+ .addStates(state)
+ .build()
+ private val traceBytes = trace.toByteArray()
+
+ @Test
+ fun canParseErrors() {
+ val errorTrace = ErrorTraceParserUtil.parseFromTrace(traceBytes)
+ val errorState = errorTrace.entries.first()
+ val error = errorState.errors.first()
+
+ assertThat(errorState.timestamp).isEqualTo(100)
+ assertThat(error.layerId).isEqualTo(1)
+ assertThat(error.taskId).isEqualTo(2)
+ assertThat(error.message).isEqualTo("Error!")
+ assertThat(error.stacktrace).isEqualTo("stacktrace of error")
+ assertThat(error.windowToken).isEqualTo("token")
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FassMockTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/FassMockTest.kt
new file mode 100644
index 000000000..a69af6491
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/FassMockTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.helpers.SampleAppHelper
+import com.android.server.wm.flicker.rules.WMFlickerServiceRule
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains a mock test for [WMFlickerServiceRule].
+ *
+ * To run this test: `atest FlickerLibTest:FassMockTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FassMockTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val dummyAppHelper = SampleAppHelper(instrumentation)
+
+ @get:Rule
+ val rule = WMFlickerServiceRuleTest()
+
+ @Test
+ fun startServiceTest() {
+ val device = UiDevice.getInstance(instrumentation)
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ device.wakeUp()
+ device.pressHome()
+ wmHelper.waitForHomeActivityVisible()
+ dummyAppHelper.launchViaIntent(wmHelper)
+ }
+
+ companion object {
+ private val DUMMY_APP = FlickerComponentName("com.google.android.apps.messaging",
+ "com.google.android.apps.messaging.ui.ConversationListActivity")
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/RotationMockTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/RotationMockTest.kt
new file mode 100644
index 000000000..fdc59bfe4
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/RotationMockTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.helpers.SampleAppHelper
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.rules.WMFlickerServiceRule
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains a rotation mock test for [WMFlickerServiceRule].
+ *
+ * To run this test: `atest FlickerLibTest:RotationMockTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotationMockTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val dummyAppHelper = SampleAppHelper(instrumentation)
+
+ @get:Rule
+ val rule = WMFlickerServiceRuleTest()
+
+ @Test
+ fun startRotationServiceTest() {
+ val device = UiDevice.getInstance(instrumentation)
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+
+ device.wakeUpAndGoToHomeScreen()
+ wmHelper.waitForHomeActivityVisible()
+ dummyAppHelper.launchViaIntent(wmHelper)
+ device.setOrientationLeft()
+ instrumentation.uiAutomation.syncInputTransactions()
+ device.setOrientationNatural()
+ instrumentation.uiAutomation.syncInputTransactions()
+ }
+
+ companion object {
+ private val DUMMY_APP = FlickerComponentName("com.google.android.apps.messaging",
+ "com.google.android.apps.messaging.ui.ConversationListActivity")
+ }
+}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/TagParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/TagParserTest.kt
new file mode 100644
index 000000000..716d7cb77
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/TagParserTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import com.android.server.wm.flicker.FlickerTagProto
+import com.android.server.wm.flicker.FlickerTagStateProto
+import com.android.server.wm.flicker.FlickerTagTraceProto
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.parser.tags.TagTraceParserUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [TagTraceParserUtil] and [FlickerTagProto] tests. To run this test: `atest
+ * FlickerLibTest:TagParserTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TagParserTest {
+ private val tag = FlickerTagProto.newBuilder()
+ .setLayerId(1)
+ .setTaskId(2)
+ .setIsStartTag(true)
+ .setId(123)
+ .setTransition(FlickerTagProto.Transition.APP_CLOSE)
+ .setWindowToken("token")
+ .build()
+ private val state = FlickerTagStateProto.newBuilder()
+ .setTimestamp(100)
+ .addTags(tag)
+ .build()
+ private val trace = FlickerTagTraceProto.newBuilder()
+ .addStates(state)
+ .build()
+ private val traceBytes = trace.toByteArray()
+
+ @Test
+ fun canParseTags() {
+ val tagTrace = TagTraceParserUtil.parseFromTrace(traceBytes)
+ val tagState = tagTrace.entries.first()
+ val tag = tagState.tags.first()
+
+ assertThat(tagState.timestamp).isEqualTo(100)
+ assertThat(tag.layerId).isEqualTo(1)
+ assertThat(tag.taskId).isEqualTo(2)
+ assertThat(tag.id).isEqualTo(123)
+ assertThat(tag.isStartTag).isTrue()
+ assertThat(tag.transition).isEqualTo(Transition.APP_CLOSE)
+ assertThat(tag.windowToken).isEqualTo("token")
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/TraceIsTaggableTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/TraceIsTaggableTest.kt
new file mode 100644
index 000000000..efddbbd64
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/TraceIsTaggableTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.helpers.SampleAppHelper
+import com.android.server.wm.flicker.monitor.withTracing
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.hasLayersAnimating
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isAppTransitionIdle
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory.isWMStateComplete
+import com.android.server.wm.traces.common.service.TaggingEngine
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class TraceIsTaggableTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val device = UiDevice.getInstance(instrumentation)
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+
+ @Test
+ fun canCreateTagsFromDeviceTrace() {
+
+ // Generates trace of opening the messaging application from home screen
+ val trace = withTracing {
+ device.pressHome()
+ SampleAppHelper(instrumentation).launchViaIntent(wmHelper)
+
+ // Wait until transition is fully completed
+ WindowManagerStateHelper().waitFor(
+ hasLayersAnimating().negate(),
+ isAppTransitionIdle(/* default display */ 0),
+ isWMStateComplete()
+ )
+ }
+
+ val engine = TaggingEngine(
+ requireNotNull(trace.wmTrace),
+ requireNotNull(trace.layersTrace)
+ ) { }
+
+ val tagStates = engine.run().entries
+ Truth.assertThat(tagStates.size).isEqualTo(2)
+
+ val startTag = tagStates.first().tags
+ val endTag = tagStates.last().tags
+ Truth.assertThat(startTag.size).isEqualTo(1)
+ Truth.assertThat(endTag.size).isEqualTo(1)
+
+ Truth.assertThat(startTag.first().transition).isEqualTo(Transition.APP_LAUNCH)
+ Truth.assertThat(endTag.first().transition).isEqualTo(Transition.APP_LAUNCH)
+ }
+}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleForTestSpecTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleForTestSpecTest.kt
new file mode 100644
index 000000000..3de48c92a
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleForTestSpecTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import android.app.Instrumentation
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
+import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WMFlickerServiceRuleForTestSpecTest(private val testSpec: FlickerTestParameter) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @FlickerBuilderProvider
+ fun emptyFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ transitions {
+ device.wakeUpAndGoToHomeScreen()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+ }
+
+ @Test
+ fun runAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 1)
+ .take(1)
+ }
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleTest.kt
new file mode 100644
index 000000000..6672ad004
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/WMFlickerServiceRuleTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import com.android.server.wm.flicker.rules.WMFlickerServiceRule
+import com.google.common.truth.Truth
+import org.junit.runner.Description
+
+/**
+ * An extension for [WMFlickerServiceRule] checking that the traces are collected.
+ */
+class WMFlickerServiceRuleTest : WMFlickerServiceRule() {
+ override fun finished(description: Description?) {
+ super.finished(description)
+
+ Truth.assertThat(wmTrace).isNotEmpty()
+ Truth.assertThat(layersTrace).isNotEmpty()
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AppLaunchAssertionsTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AppLaunchAssertionsTest.kt
new file mode 100644
index 000000000..ea6350ded
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AppLaunchAssertionsTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readTestFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains tests for App Launch assertions. To run this test:
+ * `atest FlickerLibTest:AppLaunchAssertionsTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AppLaunchAssertionsTest {
+ private val jsonByteArray = readTestFile("assertors/config.json")
+ private val assertions =
+ AssertionConfigParser.parseConfigFile(String(jsonByteArray))
+ .filter { it.transitionType == Transition.APP_LAUNCH }
+
+ private val appLaunchAssertor = TransitionAssertor(assertions) { }
+
+ @Test
+ fun testValidAppLaunchTraces() {
+ val wmTrace = readWmTraceFromFile(
+ "assertors/appLaunch/WindowManagerTrace.winscope")
+ val layersTrace = readLayerTraceFromFile(
+ "assertors/appLaunch/SurfaceFlingerTrace.winscope")
+ val errorTrace = appLaunchAssertor.analyze(VALID_APP_LAUNCH_TAG, wmTrace, layersTrace)
+
+ Truth.assertThat(errorTrace).isEmpty()
+ }
+
+ @Test
+ fun testInvalidAppLaunchTraces() {
+ val wmTrace = readWmTraceFromFile(
+ "assertors/appLaunch/WindowManagerInvalidTrace.winscope")
+ val layersTrace = readLayerTraceFromFile(
+ "assertors/appLaunch/SurfaceFlingerInvalidTrace.winscope")
+ val errorTrace = appLaunchAssertor.analyze(INVALID_APP_LAUNCH_TAG, wmTrace, layersTrace)
+
+ Truth.assertThat(errorTrace).isNotEmpty()
+ Truth.assertThat(errorTrace.entries.size).isEqualTo(1)
+ }
+
+ companion object {
+ private val VALID_APP_LAUNCH_TAG = Tag(1, Transition.APP_LAUNCH, true,
+ windowToken = "e91fbda")
+ private val INVALID_APP_LAUNCH_TAG = Tag(2, Transition.APP_LAUNCH, true,
+ windowToken = "ffc3b1f")
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParserTest.kt
new file mode 100644
index 000000000..e4a3615ad
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/AssertionConfigParserTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import com.android.server.wm.flicker.readTestFile
+import com.android.server.wm.traces.common.tags.Transition
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [AssertionConfigParser] tests. To run this test:
+ * `atest FlickerLibTest:AssertionConfigParserTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AssertionConfigParserTest {
+
+ @Test
+ fun canParseConfigFile() {
+ val jsonByteArray = readTestFile("assertors/assertionsConfig.json")
+ val assertionConfigurations = AssertionConfigParser.parseConfigFile(String(jsonByteArray))
+ Truth.assertThat(assertionConfigurations).hasSize(23)
+ Truth.assertThat(assertionConfigurations.first().transitionType)
+ .isEqualTo(Transition.ROTATION)
+ Truth.assertThat(assertionConfigurations.last().transitionType)
+ .isEqualTo(Transition.APP_LAUNCH)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/RotationAssertionsTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/RotationAssertionsTest.kt
new file mode 100644
index 000000000..d0f7ea0f1
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/RotationAssertionsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.assertors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readTestFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.tags.Tag
+import com.android.server.wm.traces.common.tags.Transition
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains tests for rotation assertions. To run this test:
+ * `atest FlickerLibTest:RotationAssertionsTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotationAssertionsTest {
+ private val jsonByteArray = readTestFile("assertors/config.json")
+ private val assertions =
+ AssertionConfigParser.parseConfigFile(String(jsonByteArray))
+ .filter { it.transitionType == Transition.ROTATION }
+
+ private val rotationAssertor = TransitionAssertor(assertions) { }
+
+ @Test
+ fun testValidRotationWmTrace() {
+ val wmTrace = readWmTraceFromFile("assertors/rotation/WindowManagerTrace.winscope")
+ val layersTrace = readLayerTraceFromFile("assertors/rotation/SurfaceFlingerTrace.winscope")
+ val errorTrace = rotationAssertor.analyze(ROTATION_TAG, wmTrace, layersTrace)
+
+ Truth.assertThat(errorTrace).isEmpty()
+ }
+
+ @Test
+ fun testValidRotationLayersTrace() {
+ val trace = readLayerTraceFromFile("assertors/rotation/SurfaceFlingerTrace.winscope")
+ val errorTrace = rotationAssertor.analyze(ROTATION_TAG, EMPTY_WM_TRACE, trace)
+
+ Truth.assertThat(errorTrace).isEmpty()
+ }
+
+ @Test
+ fun testInvalidRotationLayersTrace() {
+ val trace = readLayerTraceFromFile(
+ "assertors/rotation/SurfaceFlingerInvalidTrace.winscope")
+ val errorTrace = rotationAssertor.analyze(ROTATION_TAG, EMPTY_WM_TRACE, trace)
+
+ Truth.assertThat(errorTrace).isNotEmpty()
+ Truth.assertThat(errorTrace.entries.size).isEqualTo(1)
+ }
+
+ companion object {
+ private val EMPTY_WM_TRACE = WindowManagerTrace(emptyArray(), source = "")
+ private val ROTATION_TAG = Tag(1, Transition.ROTATION, true)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppCloseProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppCloseProcessorTest.kt
new file mode 100644
index 000000000..e0b55f803
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppCloseProcessorTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.AppCloseProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [AppCloseProcessor] tests. To run this test:
+ * `atest FlickerLibTest:AppCloseProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AppCloseProcessorTest {
+ private val processor = AppCloseProcessor { }
+
+ private val tagAppCloseByBackButton by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/appclose/backbutton/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/appclose/backbutton/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagAppCloseBySwipeUp by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/appclose/swipeup/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/appclose/swipeup/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagAppCloseBySwitchingApps by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/appclose/switchapp/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/appclose/switchapp/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsAppCloseByClosingRotatedApp by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/appclose/rotated/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/appclose/rotated/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsColdAppLaunch by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/applaunch/cold/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/applaunch/cold/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsWarmAppLaunch by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/applaunch/warm/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/applaunch/warm/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesAppCloseTagsByPressingBackButton() {
+ val tagTrace = tagAppCloseByBackButton
+ Truth.assertWithMessage("Should have 2 app close tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 9295108146952 // 0d2h34m55s108ms
+ val endTagTimestamp = 9295480256103 // 0d2h34m55s480ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesAppCloseTagsBySwipe() {
+ val tagTrace = tagAppCloseBySwipeUp
+ Truth.assertWithMessage("Should have 2 app close tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 9320574596259 // 0d2h35m20s578ms
+ val endTagTimestamp = 9321301178051 // 0d2h35m21s301ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesAppCloseTagsBySwitchingApps() {
+ val tagTrace = tagAppCloseBySwitchingApps
+ Truth.assertWithMessage("Should have 2 app close tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 4129701437903 // 0d1h8m49s701ms
+ val endTagTimestamp = 4132063745690 // 0d1h8m52s52ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesAppCloseTagsWhenAppRotated90() {
+ val tagTrace = tagsAppCloseByClosingRotatedApp
+ Truth.assertWithMessage("Should have 2 app close tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 343127388040903 // 3d23h18m47s388ms
+ val endTagTimestamp = 343128129419414 // 3d23h18m48s129ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesNoTagsOnColdAppLaunch() {
+ val tagTrace = tagsColdAppLaunch
+ Truth.assertWithMessage("Should have 0 app launch tags")
+ .that(tagTrace)
+ .hasSize(0)
+ }
+
+ @Test
+ fun generatesNoTagsOnWarmAppLaunch() {
+ val tagTrace = tagsWarmAppLaunch
+ Truth.assertWithMessage("Should have 0 app launch tags")
+ .that(tagTrace)
+ .hasSize(0)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppLaunchProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppLaunchProcessorTest.kt
new file mode 100644
index 000000000..73da69fc9
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/AppLaunchProcessorTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.AppLaunchProcessor
+import com.google.common.truth.Truth
+import org.junit.Test
+
+/**
+ * Contains [AppLaunchProcessor] tests. To run this test:
+ * `atest FlickerLibTest:AppLaunchProcessorTest`
+ */
+class AppLaunchProcessorTest {
+ private val processor = AppLaunchProcessor { }
+
+ /**
+ * Scenarios expecting tags
+ */
+ private val tagsColdAppLaunch by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/applaunch/cold/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/applaunch/cold/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsWarmAppLaunch by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/applaunch/warm/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/applaunch/warm/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsAppLaunchByIntent by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/applaunch/intent/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/applaunch/intent/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsAppLaunchWithRotation by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/applaunch/withrot/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/applaunch/withrot/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ /**
+ * Scenarios expecting no tags
+ */
+ private val tagsComposeNewMessage by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/appear/bygesture/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/appear/bygesture/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsRotation by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/rotation/verticaltohorizontal/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/rotation/verticaltohorizontal/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun tagsColdAppLaunch() {
+ val tagTrace = tagsColdAppLaunch
+ Truth.assertWithMessage("Should have 2 app launch tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 677617096496959 // Represents 7d20h13m37s96ms
+ val endTagTimestamp = 677617685370716 // Represents 7d20h13m37s685ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun tagsWarmAppLaunch() {
+ val tagTrace = tagsWarmAppLaunch
+ Truth.assertWithMessage("Should have 2 app launch tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 677630638463152 // Represents 7d20h13m50s638ms
+ val endTagTimestamp = 677631170881851 // Represents 7d20h13m51s170ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun tagsAppLaunchByIntent() {
+ val tagTrace = tagsAppLaunchByIntent
+ Truth.assertWithMessage("Should have 2 app launch tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 678152269074856 // Represents 7d20h22m32s269ms
+ val endTagTimestamp = 678152921944244 // Represents 7d20h22m32s921ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun tagsAppLaunchWithRotation() {
+ val tagTrace = tagsAppLaunchWithRotation
+ Truth.assertWithMessage("Should have 2 app launch tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 765902849663314 // Represents 8d20h45m2s849ms
+ val endTagTimestamp = 765903475287491 // Represents 8d20h45m4s139ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun doesNotTagComposeNewMessage() {
+ val tagTrace = tagsComposeNewMessage
+ Truth.assertWithMessage("Should have 0 app launch tags")
+ .that(tagTrace)
+ .isEmpty()
+ }
+
+ @Test
+ fun doesNotTagRotation() {
+ val tagTrace = tagsRotation
+ Truth.assertWithMessage("Should have 0 app launch tags")
+ .that(tagTrace)
+ .isEmpty()
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeAppearProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeAppearProcessorTest.kt
new file mode 100644
index 000000000..827a7aa67
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeAppearProcessorTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.ImeAppearProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [ImeAppearProcessor] tests. To run this test:
+ * `atest FlickerLibTest:ImeAppearProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ImeAppearProcessorTest {
+ private val processor = ImeAppearProcessor { }
+
+ private val tagsImeAppearWithGesture by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/appear/bygesture/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/appear/bygesture/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsImeAppearWithoutGestureOnAppLaunch by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/appear/applaunchnogesture/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/appear/applaunchnogesture/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val doesntTagStationaryIme by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/appear/stationary/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/appear/stationary/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsImeAppearHorizontal by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/appear/horizontal/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/appear/horizontal/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesImeTagsOnGesture() {
+ val tagTrace = tagsImeAppearWithGesture
+ Truth.assertWithMessage("Should have 2 Ime appear tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 11120377206699 // 0d3h5m20s377ms
+ val endTagTimestamp = 11120645554330 // 0d3h5m20s645ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesTagsOnAppLaunchWithNoGesture() {
+ val tagTrace = tagsImeAppearWithoutGestureOnAppLaunch
+ Truth.assertWithMessage("Should have 2 Ime appear tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 437392442580 // 0d0h7m17s392ms
+ val endTagTimestamp = 437396516487 // 0d0h7m17s396ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesTagsOnHorizontalAppLaunchWithGesture() {
+ val tagTrace = tagsImeAppearHorizontal
+ Truth.assertWithMessage("Should have 2 Ime appear tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 1015548115255262 // 11d18h5m48s115ms
+ val endTagTimestamp = 1015548233177878 // 11d18h5m48s223ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun doNotGenerateTagsOnStationaryIme() {
+ val tagTrace = doesntTagStationaryIme
+ Truth.assertWithMessage("Should have no Ime appear tags")
+ .that(tagTrace)
+ .hasSize(0)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeDisappearProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeDisappearProcessorTest.kt
new file mode 100644
index 000000000..f5aae5add
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/ImeDisappearProcessorTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.ImeDisappearProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [ImeDisappearProcessor] tests. To run this test:
+ * `atest FlickerLibTest:ImeDisappearProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ImeDisappearProcessorTest {
+ private val processor = ImeDisappearProcessor { }
+
+ private val tagsImeDisappearWithGestureOpenAndClose by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/disappear/bygesture/openandclose/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/disappear/bygesture/openandclose/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsImeDisappearOnAppOpenAndClose by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/disappear/closeapp/openandclose/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/disappear/closeapp/openandclose/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsImeDisappearWithGestureClose by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/disappear/bygesture/close/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/disappear/bygesture/close/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsImeDisappearOnAppClose by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/ime/disappear/closeapp/close/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/ime/disappear/closeapp/close/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesImeDisappearTagsWithGestureOpenAndCloseIme() {
+ val tagTrace = tagsImeDisappearWithGestureOpenAndClose
+ Truth.assertWithMessage("Should have 2 IME disappear tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 69234720627579 // 19h13m54s720ms
+ val endTagTimestamp = 69234929459162 // 19h13m54s929ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesImeDisappearTagsOnAppOpenAndClose() {
+ val tagTrace = tagsImeDisappearOnAppOpenAndClose
+ Truth.assertWithMessage("Should have 2 IME disappear tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 69524600678331 // 19h18m44s600ms
+ val endTagTimestamp = 69524958584304 // 19h18m44s958ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesImeDisappearTagsWithGestureCloseIme() {
+ val tagTrace = tagsImeDisappearWithGestureClose
+ Truth.assertWithMessage("Should have 2 IME disappear tags")
+ .that(tagTrace)
+ .hasSize(2)
+
+ val startTagTimestamp = 69387450340971 // 19h16m27s450ms
+ val endTagTimestamp = 69387644316302 // 19h16m27s644ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesImeDisappearTagsOnAppClose() {
+ val tagTrace = tagsImeDisappearOnAppClose
+ Truth.assertWithMessage("Should have 2 IME disappear tags")
+ .that(tagTrace)
+ .hasSize(2)
+
+ val startTagTimestamp = 69635457764375 // 19h20m35s457ms
+ val endTagTimestamp = 69635765486645 // 19h20m35s765ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipEnterProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipEnterProcessorTest.kt
new file mode 100644
index 000000000..32eb1bb49
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipEnterProcessorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.PipEnterProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [PipEnterProcessor] tests. To run this test:
+ * `atest FlickerLibTest:PipEnterProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipEnterProcessorTest {
+ private val processor = PipEnterProcessor {}
+
+ private val tagsPipEnterWithoutRotation by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/enter/norotation/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/enter/norotation/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsPipEnterWithRotation by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/enter/rotation/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/enter/rotation/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsPipEnterWithSplitScreen by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/enter/splitscreen/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/enter/splitscreen/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsPipEnterTwice by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/enter/twice/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/enter/twice/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsStationaryPip by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/enter/stationary/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/enter/stationary/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesPipEnterTagsWithoutRotation() {
+ val tagTrace = tagsPipEnterWithoutRotation
+ Truth.assertWithMessage("Should have 2 PIP enter tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 224969581940 // 3m44s969ms
+ val endTagTimestamp = 225315964162 // 3m45s315ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesPipEnterTagsWithRotation() {
+ val tagTrace = tagsPipEnterWithRotation
+ Truth.assertWithMessage("Should have 2 PIP enter tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 7546761373566 // 2h5m46s761ms
+ val endTagTimestamp = 7547420542538 // 2h5m47s420ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesPipEnterTagsWithSplitScreen() {
+ val tagTrace = tagsPipEnterWithSplitScreen
+ Truth.assertWithMessage("Should have 2 PIP enter tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 7706716317365 // 2h8m26s716ms
+ val endTagTimestamp = 7707029441615 // 2h8m27s29ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesPipEnterTagsTwice() {
+ val tagTrace = tagsPipEnterTwice
+ Truth.assertWithMessage("Should have 4 PIP enter tags")
+ .that(tagTrace)
+ .hasSize(4)
+ val firstPipEnter = arrayOf(126177655536, 126747555592) // 2m6s177ms, 2m6s747ms
+ val secondPipEnter = arrayOf(132780039058, 133367243856) // 2m12s780ms, 2m13s367ms
+ Truth.assertThat(tagTrace[0].timestamp).isEqualTo(firstPipEnter[0])
+ Truth.assertThat(tagTrace[1].timestamp).isEqualTo(firstPipEnter[1])
+ Truth.assertThat(tagTrace[2].timestamp).isEqualTo(secondPipEnter[0])
+ Truth.assertThat(tagTrace[3].timestamp).isEqualTo(secondPipEnter[1])
+ }
+
+ @Test
+ fun doesNotGeneratePipEnterTagsOnStationaryPip() {
+ val tagTrace = tagsStationaryPip
+ Truth.assertWithMessage("Should have no PIP enter tags")
+ .that(tagTrace)
+ .hasSize(0)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExitProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExitProcessorTest.kt
new file mode 100644
index 000000000..a0d703887
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExitProcessorTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.PipExitProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [PipExitProcessor] tests. To run this test:
+ * `atest FlickerLibTest:PipExitProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipExitProcessorTest {
+ private val processor = PipExitProcessor { }
+
+ private val tagPipExitByDismissButton by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/exit/dismissbutton/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/exit/dismissbutton/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagPipExitBySwipe by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/exit/swipe/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/exit/swipe/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagPipExpand by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/expand/expand/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/expand/expand/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesPipExitTagsByDismissButton() {
+ val tagTrace = tagPipExitByDismissButton
+ Truth.assertWithMessage("Should have 2 pip exit tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 2852929744046 // 0d0h47m32s929ms
+ val endTagTimestamp = 2853783914340 // 0d0h47m33s783ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesPipExitTagsBySwipe() {
+ val tagTrace = tagPipExitBySwipe
+ Truth.assertWithMessage("Should have 2 pip exit tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 2057767033167 // 0d0h34m17s767ms
+ val endTagTimestamp = 2058963092661 // 0d0h34m18s963ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun doesNotTagPipExitOnPipExpand() {
+ val tagTrace = tagPipExpand
+ Truth.assertWithMessage("Should have 0 pip exit tags")
+ .that(tagTrace)
+ .hasSize(0)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExpandProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExpandProcessorTest.kt
new file mode 100644
index 000000000..bca7f7152
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipExpandProcessorTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.PipExpandProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [PipExpandProcessor] tests. To run this test:
+ * `atest FlickerLibTest:PipExpandProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipExpandProcessorTest {
+ private val processor = PipExpandProcessor { }
+
+ private val tagsPipExpanding by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/expand/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/expand/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsPipEnterTwice by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/enter/twice/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/enter/twice/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesPipExpandTags() {
+ val tagTrace = tagsPipExpanding
+ Truth.assertWithMessage("Should have 2 pip expand tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 2881856703699 // 0d0h48m1s856ms
+ val endTagTimestamp = 2882177502376 // 0d0h0m48m2s177ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun doesntTagPipEnterTwice() {
+ val tagTrace = tagsPipEnterTwice
+ Truth.assertWithMessage("Should have 0 pip expand tags")
+ .that(tagTrace)
+ .hasSize(0)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipResizeProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipResizeProcessorTest.kt
new file mode 100644
index 000000000..34a87dfd8
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/PipResizeProcessorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.PipResizeProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [PipResizeProcessor] tests. To run this test:
+ * `atest FlickerLibTest:PipResizeProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipResizeProcessorTest {
+ private val processor = PipResizeProcessor { }
+
+ private val tagsPipResizingToExpand by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/resize/expand/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/resize/expand/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsPipResizingToShrink by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/pip/resize/shrink/WindowManagerTrace.winscope"
+ )
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/pip/resize/shrink/SurfaceFlingerTrace.winscope"
+ )
+ processor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun generatesPipResizeTagsOnExpand() {
+ val tagTrace = tagsPipResizingToExpand
+ Truth.assertWithMessage("Should have 2 pip resize tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 175188084434996 // 2d0h39m48s84ms
+ val endTagTimestamp = 175188414547217 // 2d0h39m48s414ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+
+ @Test
+ fun generatesPipResizeTagsOnShrink() {
+ val tagTrace = tagsPipResizingToShrink
+ Truth.assertWithMessage("Should have 2 pip resize tags")
+ .that(tagTrace)
+ .hasSize(2)
+ val startTagTimestamp = 183718779433562 // 2d3h1m58s779ms
+ val endTagTimestamp = 183719115416407 // 2d3h1m59s115ms
+ Truth.assertThat(tagTrace.first().timestamp).isEqualTo(startTagTimestamp)
+ Truth.assertThat(tagTrace.last().timestamp).isEqualTo(endTagTimestamp)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/RotationProcessorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/RotationProcessorTest.kt
new file mode 100644
index 000000000..0245a5941
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/service/processors/RotationProcessorTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.processors
+
+import com.android.server.wm.flicker.readLayerTraceFromFile
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.traces.common.service.processors.RotationProcessor
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [RotationProcessor] tests. To run this test:
+ * `atest FlickerLibTest:RotationProcessorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotationProcessorTest {
+ companion object {
+ const val REGULAR_ROTATION1_START = 280186737540384
+ const val REGULAR_ROTATION1_END = 280187243649340
+ const val REGULAR_ROTATION2_START = 280188522078113
+ const val REGULAR_ROTATION2_END = 280189020672174
+ const val SEAMLESS_ROTATION_START = 981157456801L
+ const val SEAMLESS_ROTATION_END = 981560560070L
+ const val DISPLAYS_ROTATION1_START = 67585958089516
+ const val DISPLAYS_ROTATION1_END = 67586545923169
+ const val DISPLAYS_ROTATION2_START = 67587517164151
+ const val DISPLAYS_ROTATION2_END = 67588122219420
+ }
+
+ private val rotationProcessor = RotationProcessor { }
+ private val tagsRegularRotationTagFinalState by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/rotation/regular/WindowManagerTrace.winscope")
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/rotation/regular/SurfaceFlingerTrace.winscope")
+ rotationProcessor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsSeamlessRotation by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/rotation/seamless/WindowManagerTrace.winscope")
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/rotation/seamless/SurfaceFlingerTrace.winscope")
+ rotationProcessor.generateTags(wmTrace, layersTrace)
+ }
+
+ private val tagsDisplaysRotation by lazy {
+ val wmTrace = readWmTraceFromFile(
+ "tagprocessors/rotation/displays/WindowManagerTrace.winscope")
+ val layersTrace = readLayerTraceFromFile(
+ "tagprocessors/rotation/displays/SurfaceFlingerTrace.winscope")
+ rotationProcessor.generateTags(wmTrace, layersTrace)
+ }
+
+ @Test
+ fun canDetectMultipleRegularRotations() {
+ Truth.assertWithMessage("Number of tags")
+ .that(tagsRegularRotationTagFinalState)
+ .hasSize(4)
+ val tags = tagsRegularRotationTagFinalState.flatMap { it.tags.toList() }
+ Truth.assertWithMessage("Number of start tags")
+ .that(tags.filter { it.isStartTag })
+ .hasSize(2)
+ Truth.assertWithMessage("Number of end tags")
+ .that(tags.filterNot { it.isStartTag })
+ .hasSize(2)
+ }
+
+ @Test
+ fun canDetectRegularRotation1Start() {
+ val tag = tagsRegularRotationTagFinalState
+ .firstOrNull { it.tags.any { tag -> tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("Start tag timestamp")
+ .that(tag)
+ .isEqualTo(REGULAR_ROTATION1_START)
+ }
+
+ @Test
+ fun canDetectRegularRotation1End() {
+ val tag = tagsRegularRotationTagFinalState
+ .firstOrNull { it.tags.any { tag -> !tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("End tag timestamp")
+ .that(tag)
+ .isEqualTo(REGULAR_ROTATION1_END)
+ }
+
+ @Test
+ fun canDetectRegularRotation2Start() {
+ val tag = tagsRegularRotationTagFinalState
+ .lastOrNull { it.tags.any { tag -> tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("Start tag timestamp")
+ .that(tag)
+ .isEqualTo(REGULAR_ROTATION2_START)
+ }
+
+ @Test
+ fun canDetectRegularRotation2End() {
+ val tag = tagsRegularRotationTagFinalState
+ .lastOrNull { it.tags.any { tag -> !tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("End tag timestamp")
+ .that(tag)
+ .isEqualTo(REGULAR_ROTATION2_END)
+ }
+
+ @Test
+ fun canDetectSeamlessRotation() {
+ Truth.assertWithMessage("Number of tags")
+ .that(tagsSeamlessRotation)
+ .hasSize(2)
+ val tags = tagsSeamlessRotation.flatMap { it.tags.toList() }
+ Truth.assertWithMessage("Number of start tags")
+ .that(tags.filter { it.isStartTag })
+ .hasSize(1)
+ Truth.assertWithMessage("Number of end tags")
+ .that(tags.filterNot { it.isStartTag })
+ .hasSize(1)
+ }
+
+ @Test
+ fun canDetectSeamlessRotationStart() {
+ val tag = tagsSeamlessRotation
+ .lastOrNull { it.tags.any { tag -> tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("Start tag timestamp")
+ .that(tag)
+ .isEqualTo(SEAMLESS_ROTATION_START)
+ }
+
+ @Test
+ fun canDetectSeamlessRotationEnd() {
+ val tag = tagsSeamlessRotation
+ .lastOrNull { it.tags.any { tag -> !tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("End tag timestamp")
+ .that(tag)
+ .isEqualTo(SEAMLESS_ROTATION_END)
+ }
+
+ @Test
+ fun canDetectDisplaysRotation() {
+ Truth.assertWithMessage("Number of tags")
+ .that(tagsDisplaysRotation)
+ .hasSize(4)
+ val tags = tagsDisplaysRotation.flatMap { it.tags.toList() }
+ Truth.assertWithMessage("Number of start tags")
+ .that(tags.filter { it.isStartTag })
+ .hasSize(2)
+ Truth.assertWithMessage("Number of end tags")
+ .that(tags.filterNot { it.isStartTag })
+ .hasSize(2)
+ }
+
+ @Test
+ fun canDetectDisplaysRotation1Start() {
+ val tag = tagsDisplaysRotation
+ .firstOrNull { it.tags.any { tag -> tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("Start tag timestamp")
+ .that(tag)
+ .isEqualTo(DISPLAYS_ROTATION1_START)
+ }
+
+ @Test
+ fun canDetectDisplaysRotation1End() {
+ val tag = tagsDisplaysRotation
+ .firstOrNull { it.tags.any { tag -> !tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("End tag timestamp")
+ .that(tag)
+ .isEqualTo(DISPLAYS_ROTATION1_END)
+ }
+
+ @Test
+ fun canDetectDisplaysRotation2Start() {
+ val tag = tagsDisplaysRotation
+ .lastOrNull { it.tags.any { tag -> tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("Start tag timestamp")
+ .that(tag)
+ .isEqualTo(DISPLAYS_ROTATION2_START)
+ }
+
+ @Test
+ fun canDetectDisplaysRotation2End() {
+ val tag = tagsDisplaysRotation
+ .lastOrNull { it.tags.any { tag -> !tag.isStartTag } }
+ ?.timestamp ?: 0L
+ Truth.assertWithMessage("End tag timestamp")
+ .that(tag)
+ .isEqualTo(DISPLAYS_ROTATION2_END)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateHelperTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateHelperTest.kt
index 4c18c68ac..dd6f5c26f 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerStateHelperTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateHelperTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,22 +14,27 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker
+package com.android.server.wm.flicker.windowmanager
-import android.content.ComponentName
import android.view.Display
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.readWmTraceFromDumpFile
+import com.android.server.wm.flicker.readWmTraceFromFile
import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
import com.android.server.wm.traces.common.Buffer
import com.android.server.wm.traces.common.Color
+import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.RectF
import com.android.server.wm.traces.common.Region
import com.android.server.wm.traces.common.layers.Layer
+import com.android.server.wm.traces.common.layers.LayerTraceEntry
import com.android.server.wm.traces.common.layers.LayerTraceEntryBuilder
import com.android.server.wm.traces.common.layers.Transform
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.google.common.truth.Truth
@@ -44,29 +49,28 @@ import org.junit.runners.MethodSorters
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class WindowManagerStateHelperTest {
class TestWindowManagerStateHelper(
+ _wmState: WindowManagerState,
/**
* Predicate to supply a new UI information
*/
- deviceDumpSupplier: () -> Dump,
+ deviceDumpSupplier: () -> DeviceStateDump<WindowManagerState, LayerTraceEntry>,
numRetries: Int = 5,
retryIntervalMs: Long = 500L
) : WindowManagerStateHelper(InstrumentationRegistry.getInstrumentation(),
deviceDumpSupplier, numRetries, retryIntervalMs) {
- var wmState = computeState(ignoreInvalidStates = true).wmState
- override fun computeState(ignoreInvalidStates: Boolean): Dump {
- val state = super.computeState(ignoreInvalidStates)
- wmState = state.wmState
- return state
+ var wmState: WindowManagerState = _wmState
+ private set
+
+ override fun updateCurrState(value: DeviceStateDump<WindowManagerState, LayerTraceEntry>) {
+ wmState = value.wmState
}
}
- private fun String.toComponentName() =
- ComponentName.unflattenFromString(this) ?: error("Unable to extract component name")
-
- private val chromeComponentName = ("com.android.chrome/org.chromium.chrome.browser" +
- ".firstrun.FirstRunActivity").toComponentName()
- private val simpleAppComponentName = "com.android.server.wm.flicker.testapp/.SimpleActivity"
- .toComponentName()
+ private val chromeComponent = FlickerComponentName.unflattenFromString(
+ "com.android.chrome/org.chromium.chrome.browser" +
+ ".firstrun.FirstRunActivity")
+ private val simpleAppComponentName = FlickerComponentName.unflattenFromString(
+ "com.android.server.wm.flicker.testapp/.SimpleActivity")
private fun createImaginaryLayer(name: String, index: Int, id: Int, parentId: Int): Layer {
val transform = Transform(0, Transform.Matrix(0f, 0f, 0f, 0f, 0f, 0f))
@@ -84,7 +88,7 @@ class WindowManagerStateHelperTest {
visibleRegion = Region(rect.toRect()),
activeBuffer = Buffer(1, 1, 1, 1),
flags = 0,
- _bounds = rect,
+ bounds = rect,
color = Color(0f, 0f, 0f, 1f),
_isOpaque = true,
shadowRadius = 0f,
@@ -92,7 +96,7 @@ class WindowManagerStateHelperTest {
type = "",
_screenBounds = rect,
transform = transform,
- _sourceBounds = rect,
+ sourceBounds = rect,
currFrame = 0,
effectiveScalingMode = 0,
bufferTransform = transform,
@@ -106,36 +110,35 @@ class WindowManagerStateHelperTest {
)
}
- private fun createImaginaryVisibleLayers(names: List<String>): List<Layer> {
+ private fun createImaginaryVisibleLayers(names: List<FlickerComponentName>): Array<Layer> {
val root = createImaginaryLayer("root", -1, id = "root".hashCode(), parentId = -1)
val layers = mutableListOf(root)
names.forEachIndexed { index, name ->
layers.add(
- createImaginaryLayer(name, index, id = name.hashCode(), parentId = root.id)
+ createImaginaryLayer(name.toLayerName(), index, id = name.hashCode(),
+ parentId = root.id)
)
}
- return layers
+ return layers.toTypedArray()
}
private fun WindowManagerTrace.asSupplier(
startingTimestamp: Long = 0
- ): () -> WindowManagerStateHelper.Dump {
+ ): () -> DeviceStateDump<WindowManagerState, LayerTraceEntry> {
val iterator = this.dropWhile { it.timestamp < startingTimestamp }.iterator()
return {
if (iterator.hasNext()) {
val wmState = iterator.next()
- val layerList = mutableListOf(WindowManagerStateHelper.STATUS_BAR_LAYER_NAME,
- WindowManagerStateHelper.NAV_BAR_LAYER_NAME)
+ val layerList = mutableListOf(FlickerComponentName.STATUS_BAR,
+ FlickerComponentName.NAV_BAR)
if (wmState.inputMethodWindowState?.isSurfaceShown == true) {
- layerList.add(WindowManagerStateHelper.IME_LAYER_NAME)
+ layerList.add(FlickerComponentName.IME)
}
val layerTraceEntry = LayerTraceEntryBuilder(timestamp = 0,
+ displays = emptyArray(),
layers = createImaginaryVisibleLayers(layerList)).build()
- WindowManagerStateHelper.Dump(
- wmState,
- layerTraceEntry
- )
+ DeviceStateDump(wmState, layerTraceEntry)
} else {
error("Reached the end of the trace")
}
@@ -146,35 +149,37 @@ class WindowManagerStateHelperTest {
fun canWaitForIme() {
val trace = readWmTraceFromFile("wm_trace_ime.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
try {
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isImeWindowVisible(Display.DEFAULT_DISPLAY)
+ .isNonAppWindowVisible(FlickerComponentName.IME)
error("IME state should not be available")
} catch (e: AssertionError) {
- helper.waitImeWindowShown(Display.DEFAULT_DISPLAY)
+ helper.waitImeShown(Display.DEFAULT_DISPLAY)
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isImeWindowVisible(Display.DEFAULT_DISPLAY)
+ .isNonAppWindowVisible(FlickerComponentName.IME)
}
}
@Test
fun canFailImeNotShown() {
- val supplier = readWmTraceFromFile("wm_trace_ime.pb").asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, retryIntervalMs = 1)
+ val trace = readWmTraceFromFile("wm_trace_ime.pb")
+ val supplier = trace.asSupplier()
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
try {
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isImeWindowVisible()
+ .isNonAppWindowVisible(FlickerComponentName.IME)
error("IME state should not be available")
} catch (e: AssertionError) {
- helper.waitImeWindowShown()
+ helper.waitImeShown()
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isImeWindowInvisible()
+ .isNonAppWindowVisible(FlickerComponentName.IME)
}
}
@@ -182,18 +187,18 @@ class WindowManagerStateHelperTest {
fun canWaitForWindow() {
val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
try {
WindowManagerStateSubject
.assertThat(helper.wmState)
- .contains(simpleAppComponentName)
+ .containsAppWindow(simpleAppComponentName)
error("Chrome window should not exist in the start of the trace")
} catch (e: AssertionError) {
helper.waitForVisibleWindow(simpleAppComponentName)
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isVisible(simpleAppComponentName)
+ .isAppWindowVisible(simpleAppComponentName)
}
}
@@ -201,11 +206,12 @@ class WindowManagerStateHelperTest {
fun canFailWindowNotShown() {
val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = 3, retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = 3, retryIntervalMs = 1)
try {
WindowManagerStateSubject
.assertThat(helper.wmState)
- .contains(simpleAppComponentName)
+ .containsAppWindow(simpleAppComponentName)
error("SimpleActivity window should not exist in the start of the trace")
} catch (e: AssertionError) {
helper.waitForVisibleWindow(simpleAppComponentName)
@@ -219,15 +225,15 @@ class WindowManagerStateHelperTest {
fun canDetectHomeActivityVisibility() {
val trace = readWmTraceFromFile("wm_trace_open_and_close_chrome.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
WindowManagerStateSubject
.assertThat(helper.wmState)
.isHomeActivityVisible()
- helper.waitForVisibleWindow(chromeComponentName)
+ helper.waitForVisibleWindow(chromeComponent)
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isHomeActivityVisible(false)
+ .isHomeActivityInvisible()
helper.waitForHomeActivityVisible()
WindowManagerStateSubject
.assertThat(helper.wmState)
@@ -238,29 +244,31 @@ class WindowManagerStateHelperTest {
fun canWaitActivityRemoved() {
val trace = readWmTraceFromFile("wm_trace_open_and_close_chrome.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
WindowManagerStateSubject
.assertThat(helper.wmState)
.isHomeActivityVisible()
- .notContains(chromeComponentName)
- helper.waitForVisibleWindow(chromeComponentName)
+ .notContains(chromeComponent)
+ helper.waitForVisibleWindow(chromeComponent)
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isVisible(chromeComponentName)
- helper.waitForActivityRemoved(chromeComponentName)
+ .isAppWindowVisible(chromeComponent)
+ helper.waitForActivityRemoved(chromeComponent)
WindowManagerStateSubject
.assertThat(helper.wmState)
- .notContains(chromeComponentName)
+ .notContains(chromeComponent)
.isHomeActivityVisible()
}
@Test
fun canWaitAppStateIdle() {
val trace = readWmTraceFromFile("wm_trace_open_and_close_chrome.pb")
- val supplier = trace.asSupplier(startingTimestamp = 69443911868523)
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val initialTimestamp = 69443911868523
+ val supplier = trace.asSupplier(startingTimestamp = initialTimestamp)
+ val initialEntry = trace.getEntry(initialTimestamp)
+ val helper = TestWindowManagerStateHelper(initialEntry, supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
try {
WindowManagerStateSubject
.assertThat(helper.wmState)
@@ -280,8 +288,8 @@ class WindowManagerStateHelperTest {
fun canWaitForRotation() {
val trace = readWmTraceFromFile("wm_trace_rotation.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
WindowManagerStateSubject
.assertThat(helper.wmState)
.hasRotation(Surface.ROTATION_0)
@@ -296,26 +304,6 @@ class WindowManagerStateHelperTest {
}
@Test
- fun canFailRotationNotReached() {
- val trace = readWmTraceFromFile("wm_trace_rotation.pb")
- val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
- WindowManagerStateSubject
- .assertThat(helper.wmState)
- .hasRotation(Surface.ROTATION_0)
- try {
- helper.waitForRotation(Surface.ROTATION_90)
- error("Should not have reached orientation ${Surface.ROTATION_90}")
- } catch (e: IllegalStateException) {
- WindowManagerStateSubject
- .assertThat(helper.wmState)
- .isNotRotation(Surface.ROTATION_90)
- .hasRotation(Surface.ROTATION_0)
- }
- }
-
- @Test
fun canDetectResumedActivitiesInStacks() {
val trace = readWmTraceFromDumpFile("wm_trace_resumed_activities_in_stack.pb")
val entry = trace.first()
@@ -330,11 +318,11 @@ class WindowManagerStateHelperTest {
fun canWaitForRecents() {
val trace = readWmTraceFromFile("wm_trace_open_recents.pb")
val supplier = trace.asSupplier()
- val helper = TestWindowManagerStateHelper(supplier, numRetries = trace.entries.size,
- retryIntervalMs = 1)
+ val helper = TestWindowManagerStateHelper(trace.first(), supplier,
+ numRetries = trace.entries.size, retryIntervalMs = 1)
WindowManagerStateSubject
.assertThat(helper.wmState)
- .isRecentsActivityVisible(visible = false)
+ .isRecentsActivityInvisible()
helper.waitForRecentsActivityVisible()
WindowManagerStateSubject
.assertThat(helper.wmState)
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt
new file mode 100644
index 000000000..0fe73dbdf
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.windowmanager
+
+import android.graphics.Region
+import com.android.server.wm.flicker.CHROME_SPLASH_SCREEN_COMPONENT
+import com.android.server.wm.flicker.IMAGINARY_COMPONENT
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.PIP_DISMISS_COMPONENT
+import com.android.server.wm.flicker.SCREEN_DECOR_COMPONENT
+import com.android.server.wm.flicker.SHELL_SPLIT_SCREEN_PRIMARY_COMPONENT
+import com.android.server.wm.flicker.SHELL_SPLIT_SCREEN_SECONDARY_COMPONENT
+import com.android.server.wm.flicker.WALLPAPER_COMPONENT
+import com.android.server.wm.flicker.assertFailure
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.assertions.FlickerSubject
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.flicker.traces.FlickerSubjectException
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject.Companion.assertThat
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+import java.lang.AssertionError
+
+/**
+ * Contains [WindowManagerStateSubject] tests.
+ * To run this test: `atest FlickerLibTest:WindowManagerStateSubjectTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerStateSubjectTest {
+ private val trace: WindowManagerTrace by lazy { readWmTraceFromFile("wm_trace_openchrome.pb") }
+ // Launcher is visible in fullscreen in the first frame of the trace
+ private val traceFirstFrameTimestamp = 9213763541297
+ // The first frame where the chrome splash screen is shown
+ private val traceFirstChromeFlashScreenTimestamp = 9215551505798
+ // The bounds of the display used to generate the trace [trace]
+ private val displayBounds = Region(0, 0, 1440, 2960)
+ // The region covered by the status bar in the trace
+ private val statusBarRegion = Region(0, 0, 1440, 171)
+
+ @Test
+ fun exceptionContainsDebugInfo() {
+ val error = assertThrows(AssertionError::class.java) {
+ assertThat(trace).first().frameRegion(IMAGINARY_COMPONENT)
+ }
+ Truth.assertThat(error).hasMessageThat().contains(IMAGINARY_COMPONENT.className)
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains(FlickerSubject.ASSERTION_TAG)
+ }
+
+ @Test
+ fun canDetectAboveAppWindowVisibility_isVisible() {
+ assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ .containsAboveAppWindow(FlickerComponentName.NAV_BAR)
+ .containsAboveAppWindow(SCREEN_DECOR_COMPONENT)
+ .containsAboveAppWindow(FlickerComponentName.STATUS_BAR)
+ }
+
+ @Test
+ fun canDetectAboveAppWindowVisibility_isInvisible() {
+ val subject = assertThat(trace).entry(traceFirstFrameTimestamp)
+ var failure = assertThrows(AssertionError::class.java) {
+ subject.containsAboveAppWindow(PIP_DISMISS_COMPONENT)
+ .isNonAppWindowVisible(PIP_DISMISS_COMPONENT)
+ }
+ assertFailure(failure).factValue("Is Invisible").contains("pip-dismiss-overlay")
+
+ failure = assertThrows(AssertionError::class.java) {
+ subject.containsAboveAppWindow(FlickerComponentName.NAV_BAR)
+ .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR)
+ }
+ assertFailure(failure).factValue("Is Visible").contains("NavigationBar")
+ }
+
+ @Test
+ fun canDetectWindowCoversAtLeastRegion_exactSize() {
+ val entry = assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+
+ entry.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtLeast(statusBarRegion)
+ entry.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtLeast(displayBounds)
+ }
+
+ @Test
+ fun canDetectWindowCoversAtLeastRegion_smallerRegion() {
+ val entry = assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ entry.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtLeast(Region(0, 0, 100, 100))
+ entry.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtLeast(Region(0, 0, 100, 100))
+ }
+
+ @Test
+ fun canDetectWindowCoversAtLeastRegion_largerRegion() {
+ val subject = assertThat(trace).entry(traceFirstFrameTimestamp)
+ var failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtLeast(Region(0, 0, 1441, 171))
+ }
+ assertFailure(failure).factValue("Uncovered region").contains("SkRegion((1440,0,1441,171))")
+
+ failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtLeast(Region(0, 0, 1440, 2961))
+ }
+ assertFailure(failure).factValue("Uncovered region")
+ .contains("SkRegion((0,2960,1440,2961))")
+ }
+
+ @Test
+ fun canDetectWindowCoversExactlyRegion_exactSize() {
+ val entry = assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+
+ entry.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(statusBarRegion)
+ entry.frameRegion(LAUNCHER_COMPONENT)
+ .coversExactly(displayBounds)
+ }
+
+ @Test
+ fun canDetectWindowCoversExactlyRegion_smallerRegion() {
+ val subject = assertThat(trace).entry(traceFirstFrameTimestamp)
+ var failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtMost(Region(0, 0, 100, 100))
+ }
+ assertFailure(failure).factValue("Out-of-bounds region")
+ .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
+
+ failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtMost(Region(0, 0, 100, 100))
+ }
+ assertFailure(failure).factValue("Out-of-bounds region")
+ .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
+ }
+
+ @Test
+ fun canDetectWindowCoversExactlyRegion_largerRegion() {
+ val subject = assertThat(trace).entry(traceFirstFrameTimestamp)
+ var failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtLeast(Region(0, 0, 1441, 171))
+ }
+ assertFailure(failure).factValue("Uncovered region").contains("SkRegion((1440,0,1441,171))")
+
+ failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtLeast(Region(0, 0, 1440, 2961))
+ }
+ assertFailure(failure).factValue("Uncovered region")
+ .contains("SkRegion((0,2960,1440,2961))")
+ }
+
+ @Test
+ fun canDetectWindowCoversAtMostRegion_extactSize() {
+ val entry = assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ entry.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtMost(statusBarRegion)
+ entry.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtMost(displayBounds)
+ }
+
+ @Test
+ fun canDetectWindowCoversAtMostRegion_smallerRegion() {
+ val subject = assertThat(trace).entry(traceFirstFrameTimestamp)
+ var failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtMost(Region(0, 0, 100, 100))
+ }
+ assertFailure(failure).factValue("Out-of-bounds region")
+ .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
+
+ failure = assertThrows(FlickerSubjectException::class.java) {
+ subject.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtMost(Region(0, 0, 100, 100))
+ }
+ assertFailure(failure).factValue("Out-of-bounds region")
+ .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
+ }
+
+ @Test
+ fun canDetectWindowCoversAtMostRegion_largerRegion() {
+ val entry = assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+
+ entry.frameRegion(FlickerComponentName.STATUS_BAR)
+ .coversAtMost(Region(0, 0, 1441, 171))
+ entry.frameRegion(LAUNCHER_COMPONENT)
+ .coversAtMost(Region(0, 0, 1440, 2961))
+ }
+
+ @Test
+ fun canDetectBelowAppWindowVisibility() {
+ assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ .containsNonAppWindow(WALLPAPER_COMPONENT)
+ }
+
+ @Test
+ fun canDetectAppWindowVisibility() {
+ assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ .containsAppWindow(LAUNCHER_COMPONENT)
+
+ assertThat(trace)
+ .entry(traceFirstChromeFlashScreenTimestamp)
+ .containsAppWindow(CHROME_SPLASH_SCREEN_COMPONENT)
+ }
+
+ @Test
+ fun canDetectAppWindowVisibilitySubject() {
+ val trace = readWmTraceFromFile("wm_trace_launcher_visible_background.pb")
+ val firstEntry = assertThat(trace).first()
+ val appWindowNames = firstEntry.wmState.appWindows.map { it.name }
+ firstEntry.verify("has1AppWindow").that(appWindowNames).hasSize(3)
+ firstEntry.verify("has1AppWindow").that(appWindowNames)
+ .contains("com.android.server.wm.flicker.testapp/" +
+ "com.android.server.wm.flicker.testapp.SimpleActivity")
+ }
+
+ @Test
+ fun canDetectLauncherVisibility() {
+ val trace = readWmTraceFromFile("wm_trace_launcher_visible_background.pb")
+ val subject = assertThat(trace)
+ val firstTrace = subject.first()
+ firstTrace.isAppWindowInvisible(LAUNCHER_COMPONENT)
+
+ // launcher is at the same time visible an invisible because it
+ // contains 2 windows with the exact same name
+ val lastTrace = subject.last()
+ lastTrace.isAppWindowInvisible(LAUNCHER_COMPONENT)
+
+ subject.isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ .isAppWindowInvisible(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .forAllEntries()
+
+ subject.isAppWindowInvisible(LAUNCHER_COMPONENT)
+ .forAllEntries()
+ }
+
+ @Test
+ fun canFailWithReasonForVisibilityChecks_windowNotFound() {
+ val failure = assertThrows(FlickerSubjectException::class.java) {
+ assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ .containsNonAppWindow(IMAGINARY_COMPONENT)
+ }
+ assertFailure(failure).hasMessageThat()
+ .contains(IMAGINARY_COMPONENT.packageName)
+ }
+
+ @Test
+ fun canFailWithReasonForVisibilityChecks_windowNotVisible() {
+ val failure = assertThrows(FlickerSubjectException::class.java) {
+ assertThat(trace)
+ .entry(traceFirstFrameTimestamp)
+ .containsNonAppWindow(FlickerComponentName.IME)
+ .isNonAppWindowVisible(FlickerComponentName.IME)
+ }
+ assertFailure(failure).factValue("Is Invisible")
+ .contains(FlickerComponentName.IME.packageName)
+ }
+
+ @Test
+ fun canDetectAppZOrder() {
+ assertThat(trace)
+ .entry(traceFirstChromeFlashScreenTimestamp)
+ .containsAppWindow(LAUNCHER_COMPONENT)
+ .isAppWindowVisible(LAUNCHER_COMPONENT)
+ .isAboveWindow(CHROME_SPLASH_SCREEN_COMPONENT, LAUNCHER_COMPONENT)
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ }
+
+ @Test
+ fun canFailWithReasonForZOrderChecks_windowNotOnTop() {
+ val failure = assertThrows(FlickerSubjectException::class.java) {
+ assertThat(trace)
+ .entry(traceFirstChromeFlashScreenTimestamp)
+ .isAppWindowOnTop(CHROME_SPLASH_SCREEN_COMPONENT)
+ }
+ assertFailure(failure)
+ .factValue("Found")
+ .contains(LAUNCHER_COMPONENT.packageName)
+ }
+
+ @Test
+ fun canDetectActivityVisibility() {
+ val trace = readWmTraceFromFile("wm_trace_split_screen.pb")
+ val lastEntry = assertThat(trace).last()
+ lastEntry.isAppWindowVisible(SHELL_SPLIT_SCREEN_PRIMARY_COMPONENT)
+ lastEntry.isAppWindowVisible(SHELL_SPLIT_SCREEN_SECONDARY_COMPONENT)
+ }
+} \ No newline at end of file
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt
new file mode 100644
index 000000000..60632fa61
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.windowmanager
+
+import com.android.server.wm.flicker.CHROME_COMPONENT
+import com.android.server.wm.flicker.CHROME_SPLASH_SCREEN_COMPONENT
+import com.android.server.wm.flicker.IMAGINARY_COMPONENT
+import com.android.server.wm.flicker.IME_ACTIVITY_COMPONENT
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.SCREEN_DECOR_COMPONENT
+import com.android.server.wm.flicker.WALLPAPER_COMPONENT
+import com.android.server.wm.flicker.assertFailure
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.flicker.traces.FlickerSubjectException
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject.Companion.assertThat
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [WindowManagerTraceSubject] tests. To run this test: `atest
+ * FlickerLibTest:WindowManagerTraceSubjectTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerTraceSubjectTest {
+ private val chromeTrace by lazy { readWmTraceFromFile("wm_trace_openchrome.pb") }
+ private val imeTrace by lazy { readWmTraceFromFile("wm_trace_ime.pb") }
+
+ @Test
+ fun testVisibleAppWindowForRange() {
+ assertThat(chromeTrace)
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .forRange(9213763541297L, 9215536878453L)
+
+ assertThat(chromeTrace)
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .isAppWindowInvisible(CHROME_SPLASH_SCREEN_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .then()
+ .isAppWindowOnTop(CHROME_SPLASH_SCREEN_COMPONENT)
+ .isAppWindowInvisible(LAUNCHER_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .then()
+ .isAppWindowOnTop(CHROME_COMPONENT)
+ .isAppWindowInvisible(LAUNCHER_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .forRange(9215551505798L, 9216093628925L)
+ }
+
+ @Test
+ fun testCanTransitionInAppWindow() {
+ assertThat(chromeTrace)
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .then()
+ .isAppWindowOnTop(CHROME_SPLASH_SCREEN_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .then()
+ .isAppWindowOnTop(CHROME_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .forAllEntries()
+ }
+
+ @Test
+ fun testCanDetectTransitionWithOptionalValue() {
+ val trace = readWmTraceFromFile("wm_trace_open_from_overview.pb")
+ val subject = assertThat(trace)
+ subject.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowOnTop(FlickerComponentName.SNAPSHOT)
+ .then()
+ .isAppWindowOnTop(CHROME_COMPONENT)
+ }
+
+ @Test
+ fun testCanTransitionInAppWindow_withOptional() {
+ assertThat(chromeTrace)
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .then()
+ .isAppWindowOnTop(CHROME_SPLASH_SCREEN_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .then()
+ .isAppWindowOnTop(CHROME_COMPONENT)
+ .isAboveAppWindowVisible(SCREEN_DECOR_COMPONENT)
+ .forAllEntries()
+ }
+
+ @Test
+ fun testCanInspectBeginning() {
+ assertThat(chromeTrace)
+ .first()
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .containsAboveAppWindow(SCREEN_DECOR_COMPONENT)
+ }
+
+ @Test
+ fun testCanInspectAppWindowOnTop() {
+ assertThat(chromeTrace)
+ .first()
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+
+ val failure = assertThrows(FlickerSubjectException::class.java) {
+ assertThat(chromeTrace)
+ .first()
+ .isAppWindowOnTop(IMAGINARY_COMPONENT)
+ .fail("Could not detect the top app window")
+ }
+ assertFailure(failure).hasMessageThat().contains("ImaginaryWindow")
+ }
+
+ @Test
+ fun testCanInspectEnd() {
+ assertThat(chromeTrace)
+ .last()
+ .isAppWindowOnTop(CHROME_COMPONENT)
+ .containsAboveAppWindow(SCREEN_DECOR_COMPONENT)
+ }
+
+ @Test
+ fun testCanTransitionNonAppWindow() {
+ assertThat(imeTrace)
+ .skipUntilFirstAssertion()
+ .isNonAppWindowInvisible(FlickerComponentName.IME)
+ .then()
+ .isNonAppWindowVisible(FlickerComponentName.IME)
+ .forAllEntries()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun testCanDetectOverlappingWindows() {
+ assertThat(imeTrace)
+ .noWindowsOverlap(FlickerComponentName.IME, FlickerComponentName.NAV_BAR,
+ IME_ACTIVITY_COMPONENT)
+ .forAllEntries()
+ }
+
+ @Test
+ fun testCanTransitionAboveAppWindow() {
+ assertThat(imeTrace)
+ .skipUntilFirstAssertion()
+ .isAboveAppWindowInvisible(FlickerComponentName.IME)
+ .then()
+ .isAboveAppWindowVisible(FlickerComponentName.IME)
+ .forAllEntries()
+ }
+
+ @Test
+ fun testCanTransitionBelowAppWindow() {
+ val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb")
+ assertThat(trace)
+ .skipUntilFirstAssertion()
+ .isBelowAppWindowVisible(WALLPAPER_COMPONENT)
+ .then()
+ .isBelowAppWindowInvisible(WALLPAPER_COMPONENT)
+ .forAllEntries()
+ }
+
+ @Test
+ fun testCanDetectVisibleWindowsMoreThanOneConsecutiveEntry() {
+ val trace = readWmTraceFromFile("wm_trace_valid_visible_windows.pb")
+ assertThat(trace).visibleWindowsShownMoreThanOneConsecutiveEntry().forAllEntries()
+ }
+
+ @Test
+ fun testCanAssertWindowStateSequence() {
+ val windowStates = assertThat(chromeTrace).windowStates(
+ "com.android.chrome/org.chromium.chrome.browser.firstrun.FirstRunActivity")
+ val visibilityChange = windowStates.zipWithNext { current, next ->
+ current.windowState?.isVisible != next.windowState?.isVisible
+ }
+
+ Truth.assertWithMessage("Visibility should have changed only 1x in the trace")
+ .that(visibilityChange.count { it })
+ .isEqualTo(1)
+ }
+
+ @Test
+ fun exceptionContainsDebugInfo() {
+ val error = assertThrows(AssertionError::class.java) {
+ assertThat(chromeTrace).isEmpty()
+ }
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ }
+}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceTest.kt
index 8383938c5..df9d8821f 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.kt
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceTest.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker
+package com.android.server.wm.flicker.windowmanager
+import com.android.server.wm.flicker.readTestFile
+import com.android.server.wm.flicker.readWmTraceFromFile
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
@@ -42,7 +44,7 @@ class WindowManagerTraceTest {
val firstEntry = trace.entries[0]
assertThat(firstEntry.timestamp).isEqualTo(9213763541297L)
assertThat(firstEntry.windowStates.size).isEqualTo(10)
- assertThat(firstEntry.visibleWindows.size).isEqualTo(6)
+ assertThat(firstEntry.visibleWindows.size).isEqualTo(5)
assertThat(trace.entries[trace.entries.size - 1].timestamp)
.isEqualTo(9216093628925L)
}
@@ -61,7 +63,7 @@ class WindowManagerTraceTest {
} catch (e: Exception) {
throw RuntimeException(e)
}
- assertWithMessage("Unable to parse dump").that(trace.entries).hasSize(1)
+ assertWithMessage("Unable to parse dump").that(trace).hasSize(1)
}
/**
@@ -137,4 +139,21 @@ class WindowManagerTraceTest {
assertThat(entry.getIsIncompleteReason())
.contains("No resumed activities found")
}
+
+ @Test
+ fun canFilter() {
+ val splitWmTrace = trace.filter(9215895891561, 9216093628925)
+
+ assertThat(splitWmTrace).isNotEmpty()
+
+ assertThat(splitWmTrace.entries.first().timestamp).isEqualTo(9215895891561)
+ assertThat(splitWmTrace.entries.last().timestamp).isEqualTo(9216093628925)
+ }
+
+ @Test
+ fun canFilter_wrongTimestamps() {
+ val splitWmTrace = trace.filter(71607477186189, 71607812120180)
+
+ assertThat(splitWmTrace).isEmpty()
+ }
}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt
new file mode 100644
index 000000000..b7ab53515
--- /dev/null
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.windowmanager
+
+import com.android.server.wm.flicker.IMAGINARY_COMPONENT
+import com.android.server.wm.flicker.assertThrows
+import com.android.server.wm.flicker.readWmTraceFromFile
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class WindowStateSubjectTest {
+ private val trace: WindowManagerTrace by lazy { readWmTraceFromFile("wm_trace_openchrome.pb") }
+
+ @Test
+ fun exceptionContainsDebugInfoImaginary() {
+ val error = assertThrows(AssertionError::class.java) {
+ WindowManagerTraceSubject.assertThat(trace)
+ .first()
+ .windowState(IMAGINARY_COMPONENT.className)
+ .exists()
+ }
+ Truth.assertThat(error).hasMessageThat().contains(IMAGINARY_COMPONENT.className)
+ Truth.assertThat(error).hasMessageThat().contains("What?")
+ Truth.assertThat(error).hasMessageThat().contains("Where?")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ Truth.assertThat(error).hasMessageThat().contains("Window title")
+ }
+
+ @Test
+ fun exceptionContainsDebugInfoConcrete() {
+ val error = assertThrows(AssertionError::class.java) {
+ WindowManagerTraceSubject.assertThat(trace)
+ .first()
+ .subjects
+ .first()
+ .doesNotExist()
+ }
+ Truth.assertThat(error).hasMessageThat().contains("What?")
+ Truth.assertThat(error).hasMessageThat().contains("Where?")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace start")
+ Truth.assertThat(error).hasMessageThat().contains("Trace file")
+ Truth.assertThat(error).hasMessageThat().contains("Entry")
+ }
+} \ No newline at end of file
diff --git a/libraries/health/rules/src/android/platform/test/rule/ClassMetricRule.java b/libraries/health/rules/src/android/platform/test/rule/ClassMetricRule.java
new file mode 100644
index 000000000..2a2ff5082
--- /dev/null
+++ b/libraries/health/rules/src/android/platform/test/rule/ClassMetricRule.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.rule;
+
+import android.app.Instrumentation;
+import android.device.collectors.BaseMetricListener;
+import android.os.Bundle;
+import androidx.annotation.VisibleForTesting;
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * A rule that collects class-level metrics using a supplied list of metric collectors.
+ *
+ * <p>The metric collectors are passed in using the "class-metric-collectors" option, and the rule
+ * works by invoking the correct test-level callbacks on them at the corresponding stages of the
+ * test lifecycle. The metric collectors must be subclasses of {@link BaseMetricListener}, and can
+ * be passed in by their fully qualified class name, or simple class name if they are under the
+ * {@code android.device.collectors} package (but not subpackages).
+ *
+ * <p>Multiple metric collectors are supported as comma-separated values, The order they are
+ * triggered follows this example: for {@code -e class-metric-collectors Collector1,Collector2}, the
+ * evaluation order would be {@code Collector1#testStarted()}, {@code Collector2#testStarted()}, the
+ * entire test class, {@code Collector1#testFinished()}, {@code Collector1#testFinished()}.
+ *
+ * <p>For {@code Microbenchmark}s, this rule can be dynamically injected either inside or outside
+ * hardcoded rules (see {@code Microbenchmark})'s JavaDoc).
+ *
+ * <p>Note that metrics collected from this rule are reported as run metrics. Therefore, there is
+ * the risk of metric key collision if a run contains multiple classes that report metrics under the
+ * same key. At the moment, it's the responsibility of the metric collector to prevent collision
+ * across test classes.
+ *
+ * <p>Exceptions from metric listeners are silently logged. This behavior is in accordance with the
+ * approach taken by {@link BaseMetricListener}.
+ */
+public class ClassMetricRule extends TestMetricRule {
+ @VisibleForTesting static final String METRIC_COLLECTORS_OPTION = "class-metric-collectors";
+
+ public ClassMetricRule() {
+ this(InstrumentationRegistry.getArguments(), InstrumentationRegistry.getInstrumentation());
+ }
+
+ @VisibleForTesting
+ ClassMetricRule(Bundle args, Instrumentation instrumentation) {
+ super(
+ args,
+ instrumentation,
+ METRIC_COLLECTORS_OPTION,
+ ClassMetricRule.class.getSimpleName());
+ for (BaseMetricListener listener : mMetricListeners) {
+ listener.setReportAsInstrumentationResults(true);
+ }
+ }
+}
diff --git a/libraries/health/rules/src/android/platform/test/rule/CoolDownRule.java b/libraries/health/rules/src/android/platform/test/rule/CoolDownRule.java
index fe52efb17..78c0993de 100644
--- a/libraries/health/rules/src/android/platform/test/rule/CoolDownRule.java
+++ b/libraries/health/rules/src/android/platform/test/rule/CoolDownRule.java
@@ -74,7 +74,8 @@ public class CoolDownRule extends TestWatcher {
CoolDownRule.unescapeOptionStr(
getArguments().getString(DEVICE_TEMPERATURE_NAME_OPTION, ""));
if (mDeviceTemperatureName.isEmpty()) {
- throw new IllegalArgumentException("Missed device temperature name.");
+ Log.w(LOG_TAG, "Missed device temperature name. Skipped waiting for DUT cooling down.");
+ return;
}
mPollIntervalSecs = Long.valueOf(getArguments().getString(POLL_INTERVAL_OPTION, "30"));
mMaxWaitSecs = Long.valueOf(getArguments().getString(MAX_WAIT_OPTION, "1200"));
diff --git a/libraries/health/rules/src/android/platform/test/rule/FailureWatcher.java b/libraries/health/rules/src/android/platform/test/rule/FailureWatcher.java
new file mode 100644
index 000000000..3b4681518
--- /dev/null
+++ b/libraries/health/rules/src/android/platform/test/rule/FailureWatcher.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.rule;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.runner.Description;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/** A rule that generates debug artifact files for failed tests. */
+public class FailureWatcher extends TestWatcher {
+ private static final String TAG = "FailureWatcher";
+ private final UiDevice mDevice;
+
+ public FailureWatcher() {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @Override
+ protected void failed(Throwable e, Description description) {
+ onError(mDevice, description, e);
+ }
+
+ public static File diagFile(Description description, String prefix, String ext) {
+ return new File(
+ getInstrumentation().getTargetContext().getFilesDir(),
+ prefix
+ + "-"
+ + description.getTestClass().getSimpleName()
+ + "."
+ + description.getMethodName()
+ + "."
+ + ext);
+ }
+
+ public static void onError(UiDevice device, Description description, Throwable e) {
+ if (device == null) return;
+ final File sceenshot = diagFile(description, "TestScreenshot", "png");
+ final File hierarchy = diagFile(description, "Hierarchy", "zip");
+
+ // Dump window hierarchy
+ try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) {
+ out.putNextEntry(new ZipEntry("bugreport.txt"));
+ dumpStringCommand("dumpsys window windows", out);
+ dumpStringCommand("dumpsys package", out);
+ out.closeEntry();
+
+ out.putNextEntry(new ZipEntry("visible_windows.zip"));
+ dumpCommand("cmd window dump-visible-window-views", out);
+ out.closeEntry();
+ } catch (IOException ex) {
+ }
+
+ Log.e(
+ TAG,
+ "Failed test "
+ + description.getMethodName()
+ + ",\nscreenshot will be saved to "
+ + sceenshot
+ + ",\nUI dump at: "
+ + hierarchy
+ + " (use go/web-hv to open the dump file)",
+ e);
+ device.takeScreenshot(sceenshot);
+
+ // Dump accessibility hierarchy
+ try {
+ device.dumpWindowHierarchy(diagFile(description, "AccessibilityHierarchy", "uix"));
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to save accessibility hierarchy", ex);
+ }
+ }
+
+ private static void dumpStringCommand(String cmd, OutputStream out) throws IOException {
+ out.write(("\n\n" + cmd + "\n").getBytes());
+ dumpCommand(cmd, out);
+ }
+
+ private static void dumpCommand(String cmd, OutputStream out) throws IOException {
+ try (AutoCloseInputStream in =
+ new AutoCloseInputStream(
+ getInstrumentation().getUiAutomation().executeShellCommand(cmd))) {
+ FileUtils.copy(in, out);
+ }
+ }
+}
diff --git a/libraries/health/rules/src/android/platform/test/rule/LandscapeOrientationRule.java b/libraries/health/rules/src/android/platform/test/rule/LandscapeOrientationRule.java
index d7bad3435..11fbca466 100644
--- a/libraries/health/rules/src/android/platform/test/rule/LandscapeOrientationRule.java
+++ b/libraries/health/rules/src/android/platform/test/rule/LandscapeOrientationRule.java
@@ -20,6 +20,7 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static org.junit.Assert.assertEquals;
import android.os.RemoteException;
+import android.os.SystemClock;
import org.junit.runner.Description;
@@ -36,11 +37,18 @@ public class LandscapeOrientationRule extends TestWatcher {
int currentOrientation = getContext().getResources().getConfiguration().orientation;
if (currentOrientation != ORIENTATION_LANDSCAPE) { // ORIENTATION_PORTRAIT
getUiDevice().setOrientationLeft();
- int rotatedOrientation = getContext().getResources().getConfiguration().orientation;
- assertEquals(
- "Orientation should be landscape",
- ORIENTATION_LANDSCAPE,
- rotatedOrientation);
+ for (int i = 0; i != 100; ++i) {
+ int rotatedOrientation =
+ getContext().getResources().getConfiguration().orientation;
+ if (rotatedOrientation == ORIENTATION_LANDSCAPE) break;
+ if (i == 99) {
+ assertEquals(
+ "Orientation should be landscape",
+ ORIENTATION_LANDSCAPE,
+ rotatedOrientation);
+ }
+ SystemClock.sleep(100);
+ }
}
} catch (RemoteException e) {
String message = "RemoteException when forcing landscape rotation on the device";
diff --git a/libraries/health/rules/src/android/platform/test/rule/MapsPipRule.java b/libraries/health/rules/src/android/platform/test/rule/MapsPipRule.java
new file mode 100644
index 000000000..703afa9d0
--- /dev/null
+++ b/libraries/health/rules/src/android/platform/test/rule/MapsPipRule.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.rule;
+
+import android.os.SystemClock;
+import android.platform.helpers.HelperAccessor;
+import android.platform.helpers.IMapsHelper;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.junit.runner.Description;
+
+/** This rule allows to execute CUJ while Maps in pip state. */
+public class MapsPipRule extends TestWatcher {
+
+ @VisibleForTesting static final String MAPS_SEARCH_ADDRESS = "maps-close-to-pip-address";
+ String mapsAddressOption = "Golden Gate Bridge";
+
+ @VisibleForTesting static final String MAPS_TIMEOUT = "maps-timeout";
+ long mapsTimeout = 2000;
+
+ private static HelperAccessor<IMapsHelper> sMapsHelper =
+ new HelperAccessor<>(IMapsHelper.class);
+
+ @Override
+ protected void starting(Description description) {
+ mapsAddressOption = getArguments().getString(MAPS_SEARCH_ADDRESS, "Golden Gate Bridge");
+ mapsTimeout = Long.valueOf(getArguments().getString(MAPS_TIMEOUT, "2000"));
+
+ sMapsHelper.get().open();
+ sMapsHelper.get().doSearch(mapsAddressOption);
+ sMapsHelper.get().getDirections();
+ sMapsHelper.get().startNavigation();
+ sMapsHelper.get().goToNavigatePip();
+ SystemClock.sleep(mapsTimeout);
+ }
+
+ @Override
+ protected void finished(Description description) {
+ executeShellCommand(String.format("am force-stop %s", sMapsHelper.get().getPackage()));
+ }
+}
diff --git a/libraries/health/rules/src/android/platform/test/rule/PhotoUploadRule.java b/libraries/health/rules/src/android/platform/test/rule/PhotoUploadRule.java
new file mode 100644
index 000000000..66884f58b
--- /dev/null
+++ b/libraries/health/rules/src/android/platform/test/rule/PhotoUploadRule.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.rule;
+
+import android.os.SystemClock;
+import android.platform.helpers.HelperAccessor;
+import android.platform.helpers.IGoogleCameraHelper2;
+import android.platform.helpers.IPhotosHelper;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.junit.runner.Description;
+
+/** This rule allows to execute CUJ while new picures uploading in cloud. */
+public class PhotoUploadRule extends TestWatcher {
+
+ @VisibleForTesting static final String PHOTO_COUNT = "photo-count";
+ int photoCount = 5;
+
+ @VisibleForTesting static final String TAKE_PHOTO_DELAY = "take-photo-delay";
+ long takePhotoDelay = 1000;
+
+ @VisibleForTesting static final String PHOTO_TIMEOUT = "photo-timeout";
+ long photoTimeout = 10000;
+
+ @VisibleForTesting static final String UPLOAD_PHOTO = "upload-photo";
+ boolean uploadPhoto = true;
+
+ @VisibleForTesting static final String UPLOAD_VIDEO = "upload-video";
+ boolean uploadVideo = false;
+
+ @VisibleForTesting static final String CAPTURE_VIDEO_DURATION = "capture-video-duration";
+ long captureVideoDuration = 1200000;
+
+ private static HelperAccessor<IPhotosHelper> sPhotosHelper =
+ new HelperAccessor<>(IPhotosHelper.class);
+
+ private static HelperAccessor<IGoogleCameraHelper2> sGoogleCameraHelper =
+ new HelperAccessor<>(IGoogleCameraHelper2.class);
+
+ @Override
+ protected void starting(Description description) {
+ photoCount = Integer.valueOf(getArguments().getString(PHOTO_COUNT, String.valueOf(5)));
+ photoTimeout = Long.valueOf(getArguments().getString(PHOTO_TIMEOUT, String.valueOf(10000)));
+ takePhotoDelay =
+ Long.valueOf(getArguments().getString(TAKE_PHOTO_DELAY, String.valueOf(1000)));
+ captureVideoDuration =
+ Long.valueOf(
+ getArguments().getString(CAPTURE_VIDEO_DURATION, String.valueOf(30000)));
+ uploadPhoto = Boolean.valueOf(getArguments().getString(UPLOAD_PHOTO, String.valueOf(true)));
+ uploadVideo =
+ Boolean.valueOf(getArguments().getString(UPLOAD_VIDEO, String.valueOf(false)));
+
+ sPhotosHelper.get().open();
+ sPhotosHelper.get().disableBackupMode();
+ sGoogleCameraHelper.get().open();
+ if (uploadPhoto) {
+ sGoogleCameraHelper.get().takeMultiplePhotos(photoCount, takePhotoDelay);
+ SystemClock.sleep(photoTimeout);
+ }
+ if (uploadVideo) {
+ sGoogleCameraHelper.get().clickVideoTab();
+ sGoogleCameraHelper.get().clickCameraVideoButton();
+ SystemClock.sleep(captureVideoDuration);
+ sGoogleCameraHelper.get().clickCameraVideoButton();
+ }
+ sPhotosHelper.get().open();
+ sPhotosHelper.get().enableBackupMode();
+ sPhotosHelper.get().verifyContentStartedUploading();
+ sPhotosHelper.get().exit();
+ }
+
+ @Override
+ protected void finished(Description description) {
+ sPhotosHelper.get().open();
+ sPhotosHelper.get().removeContent();
+ sPhotosHelper.get().disableBackupMode();
+ sPhotosHelper.get().exit();
+ }
+}
diff --git a/libraries/health/rules/src/android/platform/test/rule/ScreenRecordRule.java b/libraries/health/rules/src/android/platform/test/rule/ScreenRecordRule.java
new file mode 100644
index 000000000..fb9d052dd
--- /dev/null
+++ b/libraries/health/rules/src/android/platform/test/rule/ScreenRecordRule.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.rule;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Rule which captures a screen record for a test. After adding this rule to the test class, apply
+ * the annotation @ScreenRecord to individual tests
+ */
+public class ScreenRecordRule implements TestRule {
+
+ private static final String TAG = "ScreenRecordRule";
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ if (description.getAnnotation(ScreenRecord.class) == null) {
+ return base;
+ }
+
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ Instrumentation inst = getInstrumentation();
+ UiAutomation automation = inst.getUiAutomation();
+ UiDevice device = UiDevice.getInstance(inst);
+
+ File outputFile = FailureWatcher.diagFile(description, "ScreenRecord", "mp4");
+ device.executeShellCommand("killall screenrecord");
+ ParcelFileDescriptor output =
+ automation.executeShellCommand("screenrecord " + outputFile);
+ String screenRecordPid = device.executeShellCommand("pidof screenrecord");
+ try {
+ base.evaluate();
+ } finally {
+ device.executeShellCommand("kill -INT " + screenRecordPid);
+ Log.e(TAG, "Screenrecord captured at: " + outputFile);
+ output.close();
+ }
+ // Delete the file if the test was successful.
+ automation.executeShellCommand("rm " + outputFile);
+ }
+ };
+ }
+
+ /** Interface to indicate that the test should capture screenrecord */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface ScreenRecord {}
+}
diff --git a/libraries/health/rules/src/android/platform/test/rule/TestMetricRule.java b/libraries/health/rules/src/android/platform/test/rule/TestMetricRule.java
index 398972833..2098a4f75 100644
--- a/libraries/health/rules/src/android/platform/test/rule/TestMetricRule.java
+++ b/libraries/health/rules/src/android/platform/test/rule/TestMetricRule.java
@@ -15,6 +15,7 @@
*/
package android.platform.test.rule;
+import android.app.Instrumentation;
import android.device.collectors.BaseMetricListener;
import android.os.Bundle;
import android.util.Log;
@@ -50,12 +51,12 @@ import java.util.List;
* approach taken by {@link BaseMetricListener}.
*/
public class TestMetricRule extends TestWatcher {
- private static final String LOG_TAG = TestMetricRule.class.getSimpleName();
-
@VisibleForTesting static final String METRIC_COLLECTORS_OPTION = "test-metric-collectors";
@VisibleForTesting static final String METRIC_COLLECTORS_PACKAGE = "android.device.collectors";
- private List<BaseMetricListener> mMetricListeners = new ArrayList<>();
+ protected List<BaseMetricListener> mMetricListeners = new ArrayList<>();
+
+ private final String mLogTag;
public TestMetricRule() {
this(InstrumentationRegistry.getArguments());
@@ -63,8 +64,25 @@ public class TestMetricRule extends TestWatcher {
@VisibleForTesting
TestMetricRule(Bundle args) {
+ this(
+ args,
+ InstrumentationRegistry.getInstrumentation(),
+ METRIC_COLLECTORS_OPTION,
+ TestMetricRule.class.getSimpleName());
+ }
+
+ /**
+ * A constructor that allows subclasses to change out various components used at initialization
+ * time.
+ */
+ protected TestMetricRule(
+ Bundle args,
+ Instrumentation instrumentation,
+ String collectorsOptionName,
+ String logTag) {
+ mLogTag = logTag;
List<String> listenerNames =
- Arrays.asList(args.getString(METRIC_COLLECTORS_OPTION, "").split(","));
+ Arrays.asList(args.getString(collectorsOptionName, "").split(","));
for (String listenerName : listenerNames) {
if (listenerName.isEmpty()) {
continue;
@@ -73,7 +91,7 @@ public class TestMetricRule extends TestWatcher {
// We could use a regex here, but this is simpler and should work just as well.
if (listenerName.contains(".")) {
Log.i(
- LOG_TAG,
+ mLogTag,
String.format(
"Attempting to dynamically load metric collector with fully "
+ "qualified name %s.",
@@ -91,7 +109,7 @@ public class TestMetricRule extends TestWatcher {
} else {
String fullName = String.format("%s.%s", METRIC_COLLECTORS_PACKAGE, listenerName);
Log.i(
- LOG_TAG,
+ mLogTag,
String.format(
"Attempting to dynamically load metric collector with simple class "
+ "name %s (fully qualified name: %s).",
@@ -111,19 +129,21 @@ public class TestMetricRule extends TestWatcher {
}
// Initialize each listener.
for (BaseMetricListener listener : mMetricListeners) {
- listener.setInstrumentation(InstrumentationRegistry.getInstrumentation());
- listener.setupAdditionalArgs();
+ listener.setInstrumentation(instrumentation);
}
}
@Override
protected void starting(Description description) {
for (BaseMetricListener listener : mMetricListeners) {
+ listener.setUp();
+ }
+ for (BaseMetricListener listener : mMetricListeners) {
try {
listener.testStarted(description);
} catch (Exception e) {
Log.e(
- LOG_TAG,
+ mLogTag,
String.format(
"Exception from listener %s during starting().",
listener.getClass().getCanonicalName()),
@@ -139,13 +159,16 @@ public class TestMetricRule extends TestWatcher {
listener.testFinished(description);
} catch (Exception e) {
Log.e(
- LOG_TAG,
+ mLogTag,
String.format(
"Exception from listener %s during finished().",
listener.getClass().getCanonicalName()),
e);
}
}
+ for (BaseMetricListener listener : mMetricListeners) {
+ listener.cleanUp();
+ }
}
@Override
@@ -156,7 +179,7 @@ public class TestMetricRule extends TestWatcher {
listener.testFailure(failure);
} catch (Exception e) {
Log.e(
- LOG_TAG,
+ mLogTag,
String.format(
"Exception from listener %s during failed().",
listener.getClass().getCanonicalName()),
diff --git a/libraries/health/rules/src/android/platform/test/rule/YouTubePipRule.java b/libraries/health/rules/src/android/platform/test/rule/YouTubePipRule.java
new file mode 100644
index 000000000..e87277080
--- /dev/null
+++ b/libraries/health/rules/src/android/platform/test/rule/YouTubePipRule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.rule;
+
+import android.os.SystemClock;
+import android.platform.helpers.HelperAccessor;
+import android.platform.helpers.IYouTubeHelper;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.junit.runner.Description;
+
+/** This rule allows to execute CUJ while YouTube in pip state. */
+public class YouTubePipRule extends TestWatcher {
+
+ @VisibleForTesting static final String YOUTUBE_PLAYBACK_TIMEOUT = "youtube-playback-time";
+ long playbackTimeout = 2000;
+
+ @VisibleForTesting static final String VIDEO_NAME = "video-name";
+ String videoName = "test-one-hour-video";
+
+ private static HelperAccessor<IYouTubeHelper> sYouTubeHelper =
+ new HelperAccessor<>(IYouTubeHelper.class).withPrefix("YouTubeHelper");
+
+ @Override
+ protected void starting(Description description) {
+ playbackTimeout = Long.valueOf(getArguments().getString(YOUTUBE_PLAYBACK_TIMEOUT, "2000"));
+ videoName = getArguments().getString(VIDEO_NAME, "test-one-hour-video");
+
+ sYouTubeHelper.get().open();
+ sYouTubeHelper.get().goToYourVideos();
+ SystemClock.sleep(playbackTimeout);
+ sYouTubeHelper.get().playYourVideo(videoName);
+ SystemClock.sleep(playbackTimeout);
+ sYouTubeHelper.get().goToYouTubePip();
+ SystemClock.sleep(playbackTimeout);
+ }
+
+ @Override
+ protected void finished(Description description) {
+ executeShellCommand(String.format("am force-stop %s", sYouTubeHelper.get().getPackage()));
+ }
+}
diff --git a/libraries/health/rules/src/android/platform/test/rule/flicker/WindowManagerFlickerRuleCommon.java b/libraries/health/rules/src/android/platform/test/rule/flicker/WindowManagerFlickerRuleCommon.java
index 032e943c4..42a4ffb42 100644
--- a/libraries/health/rules/src/android/platform/test/rule/flicker/WindowManagerFlickerRuleCommon.java
+++ b/libraries/health/rules/src/android/platform/test/rule/flicker/WindowManagerFlickerRuleCommon.java
@@ -19,10 +19,8 @@ package android.platform.test.rule.flicker;
import android.util.Log;
import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject;
+import com.android.server.wm.traces.common.FlickerComponentName;
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace;
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper;
-
-import java.util.LinkedList;
/**
* Rule used for validating the common window manager trace based flicker assertions applicable
@@ -31,19 +29,23 @@ import java.util.LinkedList;
public class WindowManagerFlickerRuleCommon extends WindowManagerFlickerRuleBase {
private static final String TAG = WindowManagerFlickerRuleCommon.class.getSimpleName();
+ private static final FlickerComponentName NAV_BAR_COMPONENT =
+ new FlickerComponentName("", "NavigationBar0");
+ private static final FlickerComponentName STATUS_BAR_COMPONENT =
+ new FlickerComponentName("", "StatusBar");
protected void validateWMFlickerConditions(WindowManagerTrace wmTrace) {
// Verify that there’s an non-app window with names NavigationBar, StatusBar above
// the app window and is visible in all winscope log entries.
WindowManagerTraceSubject.assertThat(wmTrace)
- .showsAboveAppWindow(WindowManagerStateHelper.NAV_BAR_WINDOW_NAME)
- .showsAboveAppWindow(WindowManagerStateHelper.STATUS_BAR_WINDOW_NAME)
+ .isAboveAppWindowVisible(NAV_BAR_COMPONENT)
+ .isAboveAppWindowVisible(STATUS_BAR_COMPONENT)
.forAllEntries();
// Verify that all visible windows are visible for more than one consecutive entry
// in the log entries.
WindowManagerTraceSubject.assertThat(wmTrace)
- .visibleWindowsShownMoreThanOneConsecutiveEntry(new LinkedList<String>())
+ .visibleWindowsShownMoreThanOneConsecutiveEntry()
.forAllEntries();
Log.v(TAG, "Successfully verified the window manager flicker conditions.");
}
diff --git a/libraries/health/rules/tests/src/android/platform/test/rule/ClassMetricRuleTest.java b/libraries/health/rules/tests/src/android/platform/test/rule/ClassMetricRuleTest.java
new file mode 100644
index 000000000..ec93a37f8
--- /dev/null
+++ b/libraries/health/rules/tests/src/android/platform/test/rule/ClassMetricRuleTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.rule;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.Instrumentation;
+import android.device.collectors.BaseMetricListener;
+import android.device.collectors.DataRecord;
+import android.os.Bundle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runners.model.Statement;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.List;
+
+/**
+ * Tests for {@link ClassMetricRule}.
+ *
+ * <p>This test will focus on testing that collectors are loaded with the correct argument, and that
+ * they are reporting their results as run metrics. All the other logic has been tested in {@link
+ * TestMetricRuleTest}.
+ */
+public class ClassMetricRuleTest {
+
+ private static final Description DESCRIPTION =
+ Description.createTestDescription("class", "method");
+
+ private static final Statement TEST_STATEMENT =
+ new Statement() {
+ @Override
+ public void evaluate() {}
+ };
+
+ @Mock private Instrumentation mMockInstrumentation;
+
+ @Captor private ArgumentCaptor<Bundle> addResultsCaptor;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ }
+
+ @Test
+ public void testRunsSpecifiedCollectorsAndReportRunMetrics() throws Throwable {
+ ClassMetricRule rule =
+ createWithMetricCollectorNames(
+ "android.platform.test.rule.ClassMetricRuleTest$TestableCollector2",
+ "android.platform.test.rule.ClassMetricRuleTest$TestableCollector1");
+ rule.apply(TEST_STATEMENT, DESCRIPTION).evaluate();
+
+ // We have two metric collectors, hence results are reported two times.
+ verify(mMockInstrumentation, times(2)).addResults(addResultsCaptor.capture());
+ List<Bundle> results = addResultsCaptor.getAllValues();
+ boolean hasCollector1 = false, hasCollector2 = false;
+ for (Bundle result : results) {
+ if (result.containsKey("TestableCollector1-test")) {
+ hasCollector1 = true;
+ } else if (result.containsKey("TestableCollector2-test")) {
+ hasCollector2 = true;
+ }
+ }
+ assertTrue(hasCollector1);
+ assertTrue(hasCollector2);
+ }
+
+ @Test
+ public void testUsesTestCallbackRatherThanRunCallback() throws Throwable {
+ ClassMetricRule rule =
+ createWithMetricCollectorNames(
+ "android.platform.test.rule.ClassMetricRuleTest$TestableCollector1");
+ rule.apply(TEST_STATEMENT, DESCRIPTION).evaluate();
+
+ // We have one metric collector, hence results are reported a single time.
+ verify(mMockInstrumentation, times(1)).addResults(addResultsCaptor.capture());
+ Bundle result = addResultsCaptor.getValue();
+ assertTrue(result.containsKey("TestableCollector1-test"));
+ assertFalse(result.containsKey("TestableCollector1-run"));
+ }
+
+ private ClassMetricRule createWithMetricCollectorNames(String... names) {
+ Bundle args = new Bundle();
+ args.putString(ClassMetricRule.METRIC_COLLECTORS_OPTION, String.join(",", names));
+
+ return new ClassMetricRule(args, mMockInstrumentation);
+ }
+
+ public static class BaseTestableCollector extends BaseMetricListener {
+ private final String mName;
+
+ public BaseTestableCollector(String name) {
+ mName = name;
+ }
+
+ @Override
+ public void onTestEnd(DataRecord testData, Description description) {
+ testData.addStringMetric(mName + "-test", "value");
+ }
+
+ // This method should never be used by the rule.
+ @Override
+ public void onTestRunEnd(DataRecord runData, Result result) {
+ runData.addStringMetric(mName + "-run", "value");
+ }
+ }
+
+ public static class TestableCollector1 extends BaseTestableCollector {
+ public TestableCollector1() {
+ super(TestableCollector1.class.getSimpleName());
+ }
+ }
+
+ public static class TestableCollector2 extends BaseTestableCollector {
+ public TestableCollector2() {
+ super(TestableCollector2.class.getSimpleName());
+ }
+ }
+}
diff --git a/libraries/health/rules/tests/src/android/platform/test/rule/CoolDownRuleTest.java b/libraries/health/rules/tests/src/android/platform/test/rule/CoolDownRuleTest.java
index 3bb3104b3..d2b37759b 100644
--- a/libraries/health/rules/tests/src/android/platform/test/rule/CoolDownRuleTest.java
+++ b/libraries/health/rules/tests/src/android/platform/test/rule/CoolDownRuleTest.java
@@ -87,6 +87,16 @@ public class CoolDownRuleTest {
.inOrder();
}
+ /** Tests that this rule will be skipped if not all required parameters are available. */
+ @Test
+ public void testCoolDownFallback() throws Throwable {
+ boolean screenOn = true;
+ TestableRule rule = new TestableRule(screenOn, mThermalHelper);
+ rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd"))
+ .evaluate();
+ assertThat(rule.getOperations()).containsExactly(OPS_TEST).inOrder();
+ }
+
/** Tests that this rule will fail to cool down due to timeout as expected steps. */
@Test
public void testCoolDownTimeout() throws Throwable {
diff --git a/libraries/health/rules/tests/src/android/platform/test/rule/TestMetricRuleTest.java b/libraries/health/rules/tests/src/android/platform/test/rule/TestMetricRuleTest.java
index b8ebe1c79..506cc7e68 100644
--- a/libraries/health/rules/tests/src/android/platform/test/rule/TestMetricRuleTest.java
+++ b/libraries/health/rules/tests/src/android/platform/test/rule/TestMetricRuleTest.java
@@ -80,9 +80,11 @@ public class TestMetricRuleTest {
.containsExactly(
"TestableCollector1#setInstrumentation",
"TestableCollector1#setupAdditionalArgs",
+ "TestableCollector1#onSetUp",
String.format("Test %s: TestableCollector1#onTestStart", DESCRIPTION),
"Test execution",
- String.format("Test %s: TestableCollector1#onTestEnd", DESCRIPTION))
+ String.format("Test %s: TestableCollector1#onTestEnd", DESCRIPTION),
+ "TestableCollector1#onCleanUp")
.inOrder();
}
@@ -98,6 +100,7 @@ public class TestMetricRuleTest {
.containsExactly(
"TestableCollector1#setInstrumentation",
"TestableCollector1#setupAdditionalArgs",
+ "TestableCollector1#onSetUp",
String.format("Test %s: TestableCollector1#onTestStart", DESCRIPTION),
"Test execution",
String.format(
@@ -105,7 +108,8 @@ public class TestMetricRuleTest {
DESCRIPTION,
new Failure(
DESCRIPTION, new RuntimeException(TEST_FAILURE_MESSAGE))),
- String.format("Test %s: TestableCollector1#onTestEnd", DESCRIPTION))
+ String.format("Test %s: TestableCollector1#onTestEnd", DESCRIPTION),
+ "TestableCollector1#onCleanUp")
.inOrder();
}
@@ -121,9 +125,11 @@ public class TestMetricRuleTest {
assertThat(sLogs)
.containsExactly(
"TestableCollector1#setInstrumentation",
- "TestableCollector1#setupAdditionalArgs",
"TestableCollector2#setInstrumentation",
+ "TestableCollector1#setupAdditionalArgs",
+ "TestableCollector1#onSetUp",
"TestableCollector2#setupAdditionalArgs",
+ "TestableCollector2#onSetUp",
String.format("Test %s: TestableCollector1#onTestStart", DESCRIPTION),
String.format("Test %s: TestableCollector2#onTestStart", DESCRIPTION),
"Test execution",
@@ -134,7 +140,9 @@ public class TestMetricRuleTest {
"Test %s: TestableCollector2#onTestFail with failure %s",
DESCRIPTION, failure),
String.format("Test %s: TestableCollector1#onTestEnd", DESCRIPTION),
- String.format("Test %s: TestableCollector2#onTestEnd", DESCRIPTION))
+ String.format("Test %s: TestableCollector2#onTestEnd", DESCRIPTION),
+ "TestableCollector1#onCleanUp",
+ "TestableCollector2#onCleanUp")
.inOrder();
}
@@ -163,6 +171,29 @@ public class TestMetricRuleTest {
TestMetricRule rule = createWithMetricCollectorNames(simpleName);
}
+ @Test
+ public void testInitWithDifferentOptionName() throws Throwable {
+ String optionName = "another-" + TestMetricRule.METRIC_COLLECTORS_OPTION;
+
+ Bundle args = new Bundle();
+ args.putString(
+ optionName, "android.platform.test.rule.TestMetricRuleTest$TestableCollector1");
+ TestMetricRule rule =
+ new TestMetricRule(args, new Instrumentation(), optionName, "log tag");
+
+ rule.apply(PASSING_STATEMENT, DESCRIPTION).evaluate();
+ assertThat(sLogs)
+ .containsExactly(
+ "TestableCollector1#setInstrumentation",
+ "TestableCollector1#setupAdditionalArgs",
+ "TestableCollector1#onSetUp",
+ String.format("Test %s: TestableCollector1#onTestStart", DESCRIPTION),
+ "Test execution",
+ String.format("Test %s: TestableCollector1#onTestEnd", DESCRIPTION),
+ "TestableCollector1#onCleanUp")
+ .inOrder();
+ }
+
private TestMetricRule createWithMetricCollectorNames(String... names) {
Bundle args = new Bundle();
args.putString(TestMetricRule.METRIC_COLLECTORS_OPTION, String.join(",", names));
@@ -187,6 +218,16 @@ public class TestMetricRuleTest {
}
@Override
+ public void onSetUp() {
+ sLogs.add(String.format("%s#%s", mName, "onSetUp"));
+ }
+
+ @Override
+ public void onCleanUp() {
+ sLogs.add(String.format("%s#%s", mName, "onCleanUp"));
+ }
+
+ @Override
public void onTestStart(DataRecord testData, Description description) {
sLogs.add(String.format("Test %s: %s#%s", description, mName, "onTestStart"));
}
diff --git a/libraries/health/utils/src/android/platform/test/util/TestFilter.java b/libraries/health/utils/src/android/platform/test/util/TestFilter.java
new file mode 100644
index 000000000..8f7c257be
--- /dev/null
+++ b/libraries/health/utils/src/android/platform/test/util/TestFilter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.util;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.junit.runner.Description;
+
+/** Contains a method for filtering specific tests to run. Expects the format class#method. */
+public final class TestFilter {
+
+ private static final String LOG_TAG = TestFilter.class.getSimpleName();
+ @VisibleForTesting static final String OPTION_NAME = "filter-tests";
+
+ private TestFilter() {}
+
+ public static boolean isFilteredOrUnspecified(Bundle arguments, Description description) {
+ String testFilters = arguments.getString(OPTION_NAME);
+ // If the argument is unspecified, always return true.
+ if (testFilters == null) {
+ return true;
+ }
+
+ String displayName = description.getDisplayName();
+
+ // Test the display name against all filter arguments.
+ for (String testFilter : testFilters.split(",")) {
+ String[] filterComponents = testFilter.split("#");
+ if (filterComponents.length != 2) {
+ Log.e(
+ LOG_TAG,
+ String.format(
+ "Invalid filter-tests instrumentation argument supplied, %s.",
+ testFilter));
+ continue;
+ }
+
+ String displayNameFilter =
+ String.format(
+ "%s(%s)",
+ // method
+ filterComponents[1],
+ // class
+ filterComponents[0]);
+ if (displayNameFilter.equals(displayName)) {
+ return true;
+ }
+ }
+ // If the argument is specified and no matches, return false.
+ return false;
+ }
+}
diff --git a/libraries/health/utils/tests/src/android/platform/test/util/TestFilterTest.java b/libraries/health/utils/tests/src/android/platform/test/util/TestFilterTest.java
new file mode 100644
index 000000000..eb8c42d24
--- /dev/null
+++ b/libraries/health/utils/tests/src/android/platform/test/util/TestFilterTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class TestFilterTest {
+ private static final Description DESCRIPTION1 =
+ Description.createTestDescription("testClassA", "method1");
+ private static final Description DESCRIPTION2 =
+ Description.createTestDescription("testClassB", "method2");
+
+ @Test
+ public void testFilters_singleTest() {
+ assertThat(
+ TestFilter.isFilteredOrUnspecified(
+ buildArguments("testClassA#method1"), DESCRIPTION1))
+ .isTrue();
+ }
+
+ @Test
+ public void testFilters_multipleTests() {
+ Bundle arguments = buildArguments("testClassA#method1,testClassB#method2");
+ assertThat(TestFilter.isFilteredOrUnspecified(arguments, DESCRIPTION1)).isTrue();
+ assertThat(TestFilter.isFilteredOrUnspecified(arguments, DESCRIPTION2)).isTrue();
+ }
+
+ @Test
+ public void testFilters_allTestsWhenUnspecified() {
+ assertThat(TestFilter.isFilteredOrUnspecified(new Bundle(), DESCRIPTION1)).isTrue();
+ assertThat(TestFilter.isFilteredOrUnspecified(new Bundle(), DESCRIPTION2)).isTrue();
+ }
+
+ @Test
+ public void testDoesNotFilter_otherTest() {
+ assertThat(
+ TestFilter.isFilteredOrUnspecified(
+ buildArguments("testClassA#method1"), DESCRIPTION2))
+ .isFalse();
+ }
+
+ @Test
+ public void testDoesNotThrow_onBadArguments() {
+ // If the argument is explicitly null, then it's treated as unspecified.
+ assertThat(TestFilter.isFilteredOrUnspecified(buildArguments(null), DESCRIPTION1)).isTrue();
+ // If the argument is any other invalid input, then it just won't match.
+ assertThat(TestFilter.isFilteredOrUnspecified(buildArguments(",,,"), DESCRIPTION1))
+ .isFalse();
+ assertThat(TestFilter.isFilteredOrUnspecified(buildArguments("a#,#b"), DESCRIPTION1))
+ .isFalse();
+ }
+
+ private Bundle buildArguments(String testFilterArg) {
+ Bundle args = new Bundle();
+ args.putString(TestFilter.OPTION_NAME, testFilterArg);
+ return args;
+ }
+}
diff --git a/libraries/screenshot/Android.bp b/libraries/screenshot/Android.bp
new file mode 100644
index 000000000..80ece3944
--- /dev/null
+++ b/libraries/screenshot/Android.bp
@@ -0,0 +1,69 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "platform-screenshot-diff-test",
+ platform_apis: true,
+ optimize: {
+ enabled: false
+ },
+ srcs: [
+ "src/**/*.kt"
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "launcher-helper-lib",
+ "metrics-helper-lib",
+ "platform-screenshot-diff-core",
+ "platform-test-annotations",
+ "truth-prebuilt",
+ "ub-uiautomator",
+ ],
+ asset_dirs: ["src/androidTest/assets"],
+ test_suites: ["general-tests"],
+}
+
+java_library {
+ name: "platform-screenshot-diff-core",
+ platform_apis: true,
+ optimize: {
+ enabled: false
+ },
+ srcs: [
+ "src/main/java/platform/test/screenshot/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "platform-screenshot-diff-proto",
+ ],
+}
+
+java_library {
+ name: "platform-screenshot-diff-proto",
+ srcs: [
+ "**/*.proto",
+ ],
+ optimize: {
+ enabled: false
+ }
+}
diff --git a/libraries/screenshot/AndroidManifest.xml b/libraries/screenshot/AndroidManifest.xml
new file mode 100644
index 000000000..7f828fb65
--- /dev/null
+++ b/libraries/screenshot/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="platform.test.screenshot" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="24" />
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Android Screenshot Diff Tests"
+ android:targetPackage="platform.test.screenshot" />
+
+</manifest>
diff --git a/libraries/screenshot/OWNERS b/libraries/screenshot/OWNERS
new file mode 100644
index 000000000..9dfa2ff1b
--- /dev/null
+++ b/libraries/screenshot/OWNERS
@@ -0,0 +1,5 @@
+# Owners for /libraries/screenshot
+
+ihcinihsdk@google.com
+ramperi@google.com
+yuwu@google.com
diff --git a/libraries/screenshot/TEST_MAPPING b/libraries/screenshot/TEST_MAPPING
new file mode 100644
index 000000000..3eb8e569c
--- /dev/null
+++ b/libraries/screenshot/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "platform-screenshot-diff-test",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/libraries/screenshot/proto/src/main/proto/screenshot_result.proto b/libraries/screenshot/proto/src/main/proto/screenshot_result.proto
new file mode 100644
index 000000000..8f6f8efba
--- /dev/null
+++ b/libraries/screenshot/proto/src/main/proto/screenshot_result.proto
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package test.screenshot.proto;
+option java_package = "platform.test.screenshot.proto";
+option java_outer_classname = "ScreenshotResultProto";
+
+message ComparisonOptions {
+ // Given an RGBA color, a failure will trigger if any channel changes beyond
+ // this specified threshold.
+ // Please only set this when actual inconsistencies are encountered.
+ // To find a reasonable value, consult the logs and see what real
+ // differences were encountered. Anything above 4 is probably too much.
+ // Default: 0
+ int32 allowable_per_channel_difference = 5;
+
+ // Default: 0
+ int32 allowable_number_pixels_different = 3;
+
+ // Only compare pixels that have a nonzero alpha value in the reference image.
+ // Default: false
+ bool use_masking = 4;
+}
+
+message DiffRequest {
+ // PNG encoded image.
+ bytes image_test = 1;
+ oneof reference {
+ // Absolute file path, e.g.
+ // <RUNFILES>/google3/net/hostdatapath/common/statusz/scuba_goldens/header-1.png
+ // Use this when comparing a generated image against a golden image.
+ // An "Approve Changes" button will appear in the web UI.
+ string image_location_golden = 2;
+
+ // PNG encoded image. Use when comparing two generated images against each
+ // other.
+ bytes image_reference = 3;
+ }
+ ComparisonOptions options = 4;
+
+ // Additional metadata that will be copied verbatim to the DiffResult.
+ repeated Metadata metadata = 5;
+}
+
+message DiffResult {
+ enum Status {
+ UNSPECIFIED = 0;
+ PASSED = 1; // number_pixels_different <= allowable_number_pixels_different
+ FAILED = 2;
+ // There was no file at the golden location or it was unreadable.
+ MISSING_REFERENCE = 3;
+ FLAKY = 4; // undefined behavior for single DiffResult
+ }
+
+ message ComparisonStatistics {
+ // Copy of the ComparisonOptions from the DiffRequest.
+ ComparisonOptions comparison_options = 1;
+
+ uint32 number_pixels_compared = 2;
+
+ uint32 number_pixels_identical = 3;
+ uint32 number_pixels_similar = 4; // within color_allowance
+ // When use_masking in DiffRequest is true, number of pixels that had zero
+ // alpha in the reference image. Otherwise zero.
+ uint32 number_pixels_ignored = 5;
+ uint32 number_pixels_different = 6;
+ }
+
+ Status result_type = 1;
+
+ // See DiffRequest.image_location_golden
+ string image_location_golden = 2;
+
+ // Locations relative to output folder.
+ string image_location_test = 3;
+ string image_location_reference = 4;
+ string image_location_diff = 5;
+
+ ComparisonStatistics comparison_statistics = 6;
+ repeated Metadata metadata = 7;
+
+ // MD5 hash of the difference image.
+ string hash_diff_image = 8;
+ string unique_id = 9;
+}
+
+message Metadata {
+ string key = 1;
+ string value = 2;
+}
+
+message DiffResultList {
+ repeated DiffResult results = 1;
+}
diff --git a/libraries/screenshot/src/androidTest/assets/checkbox_checked.png b/libraries/screenshot/src/androidTest/assets/checkbox_checked.png
new file mode 100644
index 000000000..c0b7c9569
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/checkbox_checked.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox.png b/libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox.png
new file mode 100644
index 000000000..a2ecce9e5
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox_round.png b/libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox_round.png
new file mode 100644
index 000000000..1a2bf0121
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/fullscreen_checked_checkbox_round.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray.png b/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray.png
new file mode 100644
index 000000000..d793f218f
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_dark.png b/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_dark.png
new file mode 100644
index 000000000..220b87d3a
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_dark.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_moved_1px.png b/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_moved_1px.png
new file mode 100644
index 000000000..347dd3972
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/fullscreen_rect_gray_moved_1px.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/round_rect_gray.png b/libraries/screenshot/src/androidTest/assets/round_rect_gray.png
new file mode 100644
index 000000000..0f7e5c8f8
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/round_rect_gray.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/round_rect_gray_dark.png b/libraries/screenshot/src/androidTest/assets/round_rect_gray_dark.png
new file mode 100644
index 000000000..260ec77b0
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/round_rect_gray_dark.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/assets/round_rect_green.png b/libraries/screenshot/src/androidTest/assets/round_rect_green.png
new file mode 100644
index 000000000..3e3ad7832
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/assets/round_rect_green.png
Binary files differ
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt
new file mode 100644
index 000000000..2845a7245
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class GoldenImagePathManagerTest {
+
+ @Test
+ fun goldenWithContextTest() {
+ val localGoldenRoot = "/localgoldenroot/"
+ val remoteGoldenRoot = "http://remotegoldenroot/"
+ val context = InstrumentationRegistry.getInstrumentation().getContext()
+ val gim = GoldenImagePathManager(
+ context,
+ GoldenImageLocationConfig(localGoldenRoot, remoteGoldenRoot))
+ // Test for resolving device local paths.
+ val localGoldenFullImagePath = gim.goldenIdentifierResolver(
+ testName = "test1", relativePathOnly = false, localPath = true)
+ assertThat(localGoldenFullImagePath).startsWith(localGoldenRoot)
+ assertThat(localGoldenFullImagePath).endsWith("dpi/test1.png")
+ assertThat(localGoldenFullImagePath.split("/").size).isEqualTo(9)
+ // Test for resolving repo paths.
+ val repoGoldenFullImagePath = gim.goldenIdentifierResolver(
+ testName = "test2", relativePathOnly = false, localPath = false)
+ assertThat(repoGoldenFullImagePath).startsWith(remoteGoldenRoot)
+ assertThat(repoGoldenFullImagePath).endsWith("dpi/test2.png")
+ assertThat(repoGoldenFullImagePath.split("/").size).isEqualTo(10)
+ }
+
+ private fun pathContextExtractor(context: Context): String {
+ return when {
+ (context.resources.displayMetrics.densityDpi.toString().length > 0) -> "context"
+ else -> "invalidcontext"
+ }
+ }
+
+ private fun pathNoContextExtractor() = "nocontext"
+
+ @Test
+ fun pathConfigTest() {
+ val pc = PathConfig(
+ PathElementNoContext("nocontext1", true, ::pathNoContextExtractor),
+ PathElementNoContext("nocontext2", true, ::pathNoContextExtractor),
+ PathElementWithContext("context1", true, ::pathContextExtractor),
+ PathElementWithContext("context2", true, ::pathContextExtractor)
+ )
+ val context = InstrumentationRegistry.getInstrumentation().getContext()
+ val pcResolvedRelativePath = pc.resolveRelativePath(context)
+ assertThat(pcResolvedRelativePath).isEqualTo("nocontext/nocontext/context/context/")
+ }
+}
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/MSSIMMatcherTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/MSSIMMatcherTest.kt
new file mode 100644
index 000000000..d44e374f7
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/MSSIMMatcherTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.utils.loadBitmap
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class MSSIMMatcherTest {
+
+ @Test
+ fun performDiff_sameBitmaps() {
+ val first = loadBitmap("round_rect_gray")
+ val second = loadBitmap("round_rect_gray")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ // Asserts that all compared pixels are categorized as "similar".
+ assertThat(result.SSIM).isEqualTo(result.numPixelsCompared)
+ }
+
+ @Test
+ fun performDiff_checkedAgainstUnchecked() {
+ val first = loadBitmap("checkbox_checked")
+ val second = loadBitmap("round_rect_gray")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.SSIM / result.numPixelsCompared)
+ .isWithin(0.001).of(0.516)
+ }
+
+ @Test
+ fun performDiff_differentBorders() {
+ val first = loadBitmap("round_rect_gray")
+ val second = loadBitmap("round_rect_green")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.SSIM / result.numPixelsCompared)
+ .isWithin(0.001).of(0.951)
+ }
+
+ @Test
+ fun performDiff_fullscreen_differentBorders_dark() {
+ val first = loadBitmap("fullscreen_rect_gray")
+ val second = loadBitmap("fullscreen_rect_gray_dark")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.SSIM / result.numPixelsCompared)
+ .isWithin(0.001).of(0.990)
+ }
+
+ @Test
+ fun performDiff_differentBorders_dark() {
+ val first = loadBitmap("round_rect_gray")
+ val second = loadBitmap("round_rect_gray_dark")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.SSIM / result.numPixelsCompared)
+ .isWithin(0.001).of(0.960)
+ }
+
+ @Test
+ fun performDiff_fullscreen_movedToRight() {
+ val first = loadBitmap("fullscreen_rect_gray")
+ val second = loadBitmap("fullscreen_rect_gray_moved_1px")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.SSIM / result.numPixelsCompared)
+ .isWithin(0.001).of(0.695)
+ }
+
+ @Test
+ fun performDiff_fullscreen_checkboxes_differentRadius() {
+ val first = loadBitmap("fullscreen_checked_checkbox")
+ val second = loadBitmap("fullscreen_checked_checkbox_round")
+
+ val matcher = MSSIMMatcher()
+ val result = matcher.calculateSSIM(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.SSIM / result.numPixelsCompared)
+ .isWithin(0.001).of(0.921)
+ }
+}
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/PixelPerfectMatcherTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/PixelPerfectMatcherTest.kt
new file mode 100644
index 000000000..2ef10487a
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/PixelPerfectMatcherTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+import platform.test.screenshot.utils.loadBitmap
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class PixelPerfectMatcherTest {
+
+ @Test
+ fun performDiff_sameBitmaps() {
+ val first = loadBitmap("round_rect_gray")
+ val second = loadBitmap("round_rect_gray")
+
+ val matcher = PixelPerfectMatcher()
+ val result = matcher.compareBitmaps(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.matches).isTrue()
+ }
+
+ @Test
+ fun performDiff_sameSize_differentBorders() {
+ val first = loadBitmap("round_rect_gray")
+ val second = loadBitmap("round_rect_green")
+
+ val matcher = PixelPerfectMatcher()
+ val result = matcher.compareBitmaps(
+ first.toIntArray(), second.toIntArray(),
+ first.width, first.height
+ )
+
+ assertThat(result.matches).isFalse()
+ }
+}
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt
new file mode 100644
index 000000000..4778b7347
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import java.lang.AssertionError
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.screenshot.OutputFileType.IMAGE_ACTUAL
+import platform.test.screenshot.OutputFileType.IMAGE_DIFF
+import platform.test.screenshot.OutputFileType.IMAGE_EXPECTED
+import platform.test.screenshot.OutputFileType.RESULT_BIN_PROTO
+import platform.test.screenshot.OutputFileType.RESULT_PROTO
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+import platform.test.screenshot.proto.ScreenshotResultProto
+import platform.test.screenshot.utils.loadBitmap
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class ScreenshotTestRuleTest {
+
+ @get:Rule
+ val rule = ScreenshotTestRule()
+
+ @Before
+ fun setup() {
+ rule.setCustomGoldenIdResolver { goldenId ->
+ "$goldenId.png"
+ }
+ }
+
+ @Test
+ fun performDiff_sameBitmaps() {
+ val first = loadBitmap("round_rect_gray")
+
+ first
+ .assertAgainstGolden(rule, "round_rect_gray", matcher = PixelPerfectMatcher())
+
+ val resultProto = rule.getPathOnDeviceFor(RESULT_PROTO)
+ assertThat(resultProto.readText()).contains("PASS")
+ assertThat(rule.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_DIFF).exists()).isFalse()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue()
+ }
+
+ @Test
+ fun performDiff_sameSizes_default_noMatch() {
+ val first = loadBitmap("round_rect_gray")
+ val compStatistics = ScreenshotResultProto.DiffResult.ComparisonStatistics.newBuilder()
+ .setNumberPixelsCompared(17)
+ .setNumberPixelsDifferent(1)
+ .setNumberPixelsIgnored(80)
+ .setNumberPixelsSimilar(16)
+ .build()
+
+ expectErrorMessage(
+ "Image mismatch! Comparison stats: '$compStatistics'"
+ ) {
+ first.assertAgainstGolden(rule, "round_rect_green")
+ }
+
+ val resultProto = rule.getPathOnDeviceFor(RESULT_PROTO)
+ assertThat(resultProto.readText()).contains("FAILED")
+ assertThat(rule.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_DIFF).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue()
+ }
+
+ @Test
+ fun performDiff_sameSizes_pixelPerfect_noMatch() {
+ val first = loadBitmap("round_rect_gray")
+ val compStatistics = ScreenshotResultProto.DiffResult.ComparisonStatistics.newBuilder()
+ .setNumberPixelsCompared(2304)
+ .setNumberPixelsDifferent(556)
+ .setNumberPixelsIdentical(1748)
+ .build()
+
+ expectErrorMessage(
+ "Image mismatch! Comparison stats: '$compStatistics'"
+ ) {
+ first
+ .assertAgainstGolden(rule, "round_rect_green", matcher = PixelPerfectMatcher())
+ }
+
+ val resultProto = rule.getPathOnDeviceFor(RESULT_PROTO)
+ assertThat(resultProto.readText()).contains("FAILED")
+ assertThat(rule.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_DIFF).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue()
+ }
+
+ @Test
+ fun performDiff_differentSizes() {
+ val first =
+ loadBitmap("fullscreen_rect_gray")
+
+ expectErrorMessage("Sizes are different! Expected: [48, 48], Actual: [720, 1184]") {
+ first
+ .assertAgainstGolden(rule, "round_rect_gray")
+ }
+
+ val resultProto = rule.getPathOnDeviceFor(RESULT_PROTO)
+ assertThat(resultProto.readText()).contains("FAILED")
+ assertThat(rule.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_DIFF).exists()).isFalse()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue()
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun performDiff_incorrectGoldenName() {
+ val first =
+ loadBitmap("fullscreen_rect_gray")
+
+ first
+ .assertAgainstGolden(rule, "round_rect_gray #")
+ }
+
+ @Test
+ fun performDiff_missingGolden() {
+ val first = loadBitmap("round_rect_gray")
+
+ expectErrorMessage(
+ "Missing golden image 'does_not_exist.png'. Did you mean to check in " +
+ "a new image?"
+ ) {
+ first
+ .assertAgainstGolden(rule, "does_not_exist")
+ }
+
+ val resultProto = rule.getPathOnDeviceFor(RESULT_PROTO)
+ assertThat(resultProto.readText()).contains("MISSING_REFERENCE")
+ assertThat(rule.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_DIFF).exists()).isFalse()
+ assertThat(rule.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isFalse()
+ assertThat(rule.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue()
+ }
+
+ @After
+ fun after() {
+ rule.clearCustomGoldenIdResolver()
+ // Clear all files we generated so we don't have dependencies between tests
+ rule.deviceOutputDirectory.deleteRecursively()
+ }
+
+ private fun expectErrorMessage(expectedErrorMessage: String, block: () -> Unit) {
+ try {
+ block()
+ } catch (e: AssertionError) {
+ val received = e.localizedMessage!!
+ assertThat(received).isEqualTo(expectedErrorMessage.trim())
+ return
+ }
+
+ throw AssertionError("No AssertionError thrown!")
+ }
+}
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/utils/BitmapUtils.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/utils/BitmapUtils.kt
new file mode 100644
index 000000000..8d3471b42
--- /dev/null
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/utils/BitmapUtils.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot.utils
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import androidx.test.platform.app.InstrumentationRegistry
+
+internal fun loadBitmap(imageName: String): Bitmap {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+ context.assets.open("$imageName.png").use {
+ return BitmapFactory.decodeStream(it)
+ }
+}
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
new file mode 100644
index 000000000..1f4e7755d
--- /dev/null
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+import android.content.Context
+import android.os.Build
+
+import java.io.File
+
+private const val BRAND_TAG = "brand"
+private const val MODEL_TAG = "model"
+private const val API_TAG = "api"
+private const val SIZE_TAG = "size"
+private const val RESOLUTION_TAG = "resolution"
+
+/**
+ * Class to manage Directory structure of golden images.
+ *
+ * When you run a AR Diff test, different attributes/dimensions of the platform you are running
+ * on, such as build, screen resolution, orientation etc. will/may render differently and therefore
+ * may require a different golden image to compare against. You can manage these multiple golden
+ * images related to your test using this utility class. It supports both device-less or device based
+ * configurations. Please see GoldenImagePathManagerTest for detailed examples.
+ *
+ * You can configure where to find the golden images repo and local cache using [locationConfig]
+ * All the goldens are stored under a directory structure, which is determined by
+ * [pathConfig]. See getDefaultPathConfig for the current implementation.
+ *
+ * There are two ways to modify how the golden images are stored and retrieved for your test:
+ * A. (Recommended) Create your own PathConfig object which takes a series of [PathElement]s
+ * Each path element represents a dimension such as screen resolution that affects the golden
+ * image. This dimension will be embedded either into the directory structure or into the
+ * filename itself. Your test can also provide its own custom implementation of [PathElement]
+ * if the dimension your test needs to rely on, is not supported.
+ * B. If you have a completely unique way of managing your golden image repository and
+ * corresponding local cache, implement a derived class and override the
+ * goldenIdentifierResolver function.
+ *
+ * NOTE: This class does not determine what combinations of attributes / dimensions your
+ * test code will run for. That decision/configuration is part of your test configuration.
+ *
+ */
+open class GoldenImagePathManager(
+ val appContext: Context,
+ val locationConfig: GoldenImageLocationConfig = GoldenImageLocationConfig(
+ getDeviceOutputDirectory(appContext),
+ getRepoURL()
+ ),
+ val pathConfig: PathConfig = getDefaultPathConfig()
+) {
+
+ private val deviceLocalPath = locationConfig.deviceLocalPath
+ private val repoRemotePath = locationConfig.repoRemotePath
+ private val imageExtension = "png"
+
+ /*
+ * Uses [pathConfig] and [testName] to construct the full path to the golden image.
+ */
+ public fun goldenIdentifierResolver(
+ testName: String,
+ relativePathOnly: Boolean = true,
+ localPath: Boolean = true
+ ): String {
+ val relativePath = pathConfig.resolveRelativePath(appContext)
+ val imageFullPath = "$relativePath$testName.$imageExtension"
+ return when {
+ relativePathOnly -> imageFullPath
+ localPath -> "$deviceLocalPath/$imageFullPath"
+ else -> "$repoRemotePath/$imageFullPath"
+ }
+ }
+}
+
+/*
+ * Every dimension that impacts the golden image needs to be a part of the path/filename
+ * that is used to access the golden. There are two types of attributes / dimensions.
+ * One that depend on the device context and the once that are context agnostic.
+ */
+abstract sealed class PathElementBase {
+ abstract val attr: String
+ abstract val isDir: Boolean
+}
+
+/*
+ * For dimensions that do not need access to the device context e.g.
+ * Build.MODEL, please instantiate the no context class.
+ */
+data class PathElementNoContext(
+ override val attr: String,
+ override val isDir: Boolean,
+ val func: (() -> String)
+) : PathElementBase()
+
+/*
+ * For dimensions that do not need to the device context e.g.
+ * and / or can change during run-time, please instantiate this class.
+ * e.g. screen orientation.
+ */
+data class PathElementWithContext(
+ override val attr: String,
+ override val isDir: Boolean,
+ val func: ((Context) -> String)
+) : PathElementBase()
+
+/*
+ * Converts an ordered list of PathElements into a relative path on filesystem.
+ * The relative path is then combined with either repo path of local cache path
+ * to get the full path to golden image.
+ */
+class PathConfig(vararg elems: PathElementBase) {
+ val data = listOf(*elems)
+
+ public fun resolveRelativePath(context: Context): String {
+ return data.map {
+ when (it) {
+ is PathElementWithContext -> it.func(context)
+ is PathElementNoContext -> it.func()
+ else -> ""
+ } + if (it.isDir) "/" else "_"
+ }.joinToString("")
+ }
+}
+
+/*
+* This configure is used to determine where to retrieve the golden images from.
+* The golden images will be stored in a git repository and will be download to the
+* device as needed. Both repo location and local path are part of this configuration.
+*/
+data class GoldenImageLocationConfig(
+ // Directory on the device that is used to store the output files.
+ val deviceLocalPath: String = "",
+
+ // Repo where all the golden images are checkedin.
+ val repoRemotePath: String = ""
+)
+
+/*
+* This is the PathConfig that will be used by default.
+* An example directory structure using this config would be
+* /google/pixel6/api32/600_400/
+*/
+
+private fun getDefaultPathConfig(): PathConfig {
+ return PathConfig(
+ PathElementNoContext(BRAND_TAG, true, ::getDeviceBrand),
+ PathElementNoContext(MODEL_TAG, true, ::getDeviceModel),
+ PathElementNoContext(API_TAG, true, ::getAPIVersion),
+ PathElementWithContext(SIZE_TAG, true, ::getScreenSize),
+ PathElementWithContext(
+ RESOLUTION_TAG,
+ true,
+ ::getScreenResolution)
+ )
+}
+
+/*
+ * Default output directory where all images generated as part of the test are stored.
+ */
+fun getDeviceOutputDirectory(context: Context) =
+ File(context.externalCacheDir, "androidx_screenshots").toString()
+
+private fun getRepoURL() = "https://"
+
+/* Standard implementations for the usual list of dimensions that affect a golden image. */
+private fun getDeviceModel(): String {
+ var model = Build.MODEL.lowercase()
+ arrayOf("phone", "x86_64", "x86", "x64", "gms").forEach {
+ model = model.replace(it, "")
+ }
+ return model.trim().replace(" ", "_")
+}
+
+private fun getDeviceBrand(): String {
+ var brand = Build.BRAND.lowercase()
+ arrayOf("phone", "x86_64", "x86", "x64", "gms").forEach {
+ brand = brand.replace(it, "")
+ }
+ return brand.trim().replace(" ", "_")
+}
+
+private fun getAPIVersion() = "API" + Build.VERSION.SDK_INT.toString()
+
+private fun getScreenResolution(context: Context) =
+ context.resources.displayMetrics.densityDpi.toString() + "dpi"
+
+private fun getScreenOrientation(context: Context) =
+ context.resources.configuration.orientation.toString()
+
+private fun getScreenSize(context: Context): String {
+ val heightdp = context.resources.configuration.screenHeightDp.toString()
+ val widthdp = context.resources.configuration.screenWidthDp.toString()
+ return "${heightdp}_$widthdp"
+}
+
+/*
+ * If the dimension that your golden depends on, is not supported,
+ * Please add its implementations here.
+ */
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/PlatformScreenshotTestRule.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/PlatformScreenshotTestRule.kt
new file mode 100644
index 000000000..891b98d74
--- /dev/null
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/PlatformScreenshotTestRule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+/**
+ * Rule to be used in platform project tests. Set's up the proper repository name and golden
+ * directory.
+ *
+ * @param moduleDirectory Directory to be used for the module that contains the tests. This is
+ * just a helper to avoid mixing goldens between different projects.
+ * Example for module directory: "compose/material/material"
+ * @param outputRootDir The root directory for output files.
+ *
+ * @hide
+ */
+class PlatformScreenshotTestRule(
+ moduleDirectory: String,
+ outputRootDir: String? = null
+) : ScreenshotTestRule(
+ ScreenshotTestRuleConfig(
+ "platform/frameworks/support-golden",
+ moduleDirectory.trim('/')
+ ),
+ outputRootDir
+)
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt
new file mode 100644
index 000000000..9f80a5345
--- /dev/null
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot
+
+import android.annotation.SuppressLint
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Build
+import android.os.Bundle
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.matchers.BitmapMatcher
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+import platform.test.screenshot.proto.ScreenshotResultProto
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+
+// TODO(b/223901506): Replace this with the more advanced config class after the CL ag/17587688
+// is submitted.
+/**
+ * Config for [ScreenshotTestRule].
+ *
+ * To be used to set up paths to golden images. These paths are not used to retrieve the goldens
+ * during the test. They are just directly stored into the result proto file. The proto file can
+ * then be used by CI to determined where to put the new approved goldens. Your tests assets
+ * directory should be pointing to exactly the same path.
+ *
+ * @param repoRootPathForGoldens Path to the repo's root that contains the goldens. To be used by
+ * CI.
+ * @param pathToGoldensInRepo Relative path to goldens inside your [repoRootPathForGoldens].
+ */
+class ScreenshotTestRuleConfig(
+ val repoRootPathForGoldens: String = "",
+ val pathToGoldensInRepo: String = ""
+)
+
+/**
+ * Type of file that can be produced by the [ScreenshotTestRule].
+ */
+internal enum class OutputFileType {
+ IMAGE_ACTUAL,
+ IMAGE_EXPECTED,
+ IMAGE_DIFF,
+ RESULT_PROTO,
+ RESULT_BIN_PROTO
+}
+
+/**
+ * Rule to be added to a test to facilitate screenshot testing.
+ *
+ * This rule records current test name and when instructed it will perform the given bitmap
+ * comparison against the given golden. All the results (including result proto file) are stored
+ * into the device to be retrieved later.
+ *
+ * @param config To configure where this rule should look for goldens.
+ * @param outputRootDir The root directory for output files.
+ *
+ * @see Bitmap.assertAgainstGolden
+ */
+@SuppressLint("SyntheticAccessor")
+open class ScreenshotTestRule(
+ val config: ScreenshotTestRuleConfig = ScreenshotTestRuleConfig(),
+ val outputRootDir: String? = null
+) : TestRule {
+
+ val deviceOutputRootDirectory: File? =
+ if (outputRootDir != null) {
+ File(outputRootDir)
+ } else {
+ InstrumentationRegistry.getInstrumentation().getContext().externalCacheDir
+ }
+
+ /**
+ * Directory on the device that is used to store the output files.
+ */
+ val deviceOutputDirectory
+ get() = File(
+ deviceOutputRootDirectory,
+ "platform_screenshots"
+ )
+
+ private val repoRootPathForGoldens = config.repoRootPathForGoldens.trim('/')
+ private val pathToGoldensInRepo = config.pathToGoldensInRepo.trim('/')
+ private val imageExtension = ".png"
+ private val resultBinaryProtoFileSuffix = ".pb"
+ // This is used in CI to identify the files.
+ private val resultProtoFileSuffix = "goldResult.textproto"
+
+ // Magic number for an in-progress status report
+ private val bundleStatusInProgress = 2
+ private val bundleKeyPrefix = "platform_screenshots_"
+
+ private lateinit var testIdentifier: String
+ private lateinit var deviceId: String
+
+ private var goldenIdentifierResolver: ((String) -> String) = ::resolveGoldenName
+
+ private val testWatcher = object : TestWatcher() {
+ override fun starting(description: Description?) {
+ deviceId = getDeviceModel()
+ testIdentifier = "${description!!.className}_${description.methodName}_$deviceId"
+ }
+ }
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ return ScreenshotTestStatement(base)
+ .run { testWatcher.apply(this, description) }
+ }
+
+ class ScreenshotTestStatement(private val base: Statement) : Statement() {
+ override fun evaluate() {
+ // NOTE(ihcinihsdk@): My hunch is that we should not add these assumptions at all.
+ // The reason is that this framework should be served for the general platform.
+ // If a test is supposed to run on a specific type of device, it should be the
+ // test authors' duty. What we need to do is to provide guidance on how to add
+ // assumptions on type device and SDK version.
+ base.evaluate()
+ }
+ }
+
+ internal fun setCustomGoldenIdResolver(resolver: ((String) -> String)) {
+ goldenIdentifierResolver = resolver
+ }
+
+ internal fun clearCustomGoldenIdResolver() {
+ goldenIdentifierResolver = ::resolveGoldenName
+ }
+
+ private fun resolveGoldenName(goldenIdentifier: String): String {
+ return "${goldenIdentifier}_$deviceId$imageExtension"
+ }
+
+ private fun fetchExpectedImage(goldenIdentifier: String): Bitmap? {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+
+ try {
+ context.assets.open(goldenIdentifierResolver(goldenIdentifier)).use {
+ return BitmapFactory.decodeStream(it)
+ }
+ } catch (e: FileNotFoundException) {
+ // Golden not present
+ return null
+ }
+ }
+
+ /**
+ * Asserts the given bitmap against the golden identified by the given name.
+ *
+ * Note: The golden identifier should be unique per your test module (unless you want multiple
+ * tests to match the same golden). The name must not contain extension. You should also avoid
+ * adding strings like "golden", "image" and instead describe what is the golder referring to.
+ *
+ * @param actual The bitmap captured during the test.
+ * @param goldenIdentifier Name of the golden. Allowed characters: 'A-Za-z0-9_-'
+ * @param matcher The algorithm to be used to perform the matching.
+ *
+ * @see MSSIMMatcher
+ * @see PixelPerfectMatcher
+ * @see Bitmap.assertAgainstGolden
+ *
+ * @throws IllegalArgumentException If the golden identifier contains forbidden characters or
+ * is empty.
+ */
+ fun assertBitmapAgainstGolden(
+ actual: Bitmap,
+ goldenIdentifier: String,
+ matcher: BitmapMatcher
+ ) {
+ if (!goldenIdentifier.matches("^[A-Za-z0-9_-]+$".toRegex())) {
+ throw IllegalArgumentException(
+ "The given golden identifier '$goldenIdentifier' does not satisfy the naming " +
+ "requirement. Allowed characters are: '[A-Za-z0-9_-]'"
+ )
+ }
+
+ val expected = fetchExpectedImage(goldenIdentifier)
+ if (expected == null) {
+ reportResult(
+ status = ScreenshotResultProto.DiffResult.Status.MISSING_REFERENCE,
+ goldenIdentifier = goldenIdentifier,
+ actual = actual
+ )
+ throw AssertionError(
+ "Missing golden image " +
+ "'${goldenIdentifierResolver(goldenIdentifier)}'. " +
+ "Did you mean to check in a new image?"
+ )
+ }
+
+ if (actual.width != expected.width || actual.height != expected.height) {
+ reportResult(
+ status = ScreenshotResultProto.DiffResult.Status.FAILED,
+ goldenIdentifier = goldenIdentifier,
+ actual = actual,
+ expected = expected
+ )
+ throw AssertionError(
+ "Sizes are different! Expected: [${expected.width}, ${expected
+ .height}], Actual: [${actual.width}, ${actual.height}]"
+ )
+ }
+
+ val comparisonResult = matcher.compareBitmaps(
+ expected = expected.toIntArray(),
+ given = actual.toIntArray(),
+ width = actual.width,
+ height = actual.height
+ )
+
+ val status = if (comparisonResult.matches) {
+ ScreenshotResultProto.DiffResult.Status.PASSED
+ } else {
+ ScreenshotResultProto.DiffResult.Status.FAILED
+ }
+
+ reportResult(
+ status = status,
+ goldenIdentifier = goldenIdentifier,
+ actual = actual,
+ comparisonStatistics = comparisonResult.comparisonStatistics,
+ expected = expected,
+ diff = comparisonResult.diff
+ )
+
+ if (!comparisonResult.matches) {
+ throw AssertionError(
+ "Image mismatch! Comparison stats: '${comparisonResult
+ .comparisonStatistics}'"
+ )
+ }
+ }
+
+ private fun reportResult(
+ status: ScreenshotResultProto.DiffResult.Status,
+ goldenIdentifier: String,
+ actual: Bitmap,
+ comparisonStatistics: ScreenshotResultProto.DiffResult.ComparisonStatistics? = null,
+ expected: Bitmap? = null,
+ diff: Bitmap? = null
+ ) {
+ val resultProto = ScreenshotResultProto.DiffResult
+ .newBuilder()
+ .setResultType(status)
+ .addMetadata(
+ ScreenshotResultProto.Metadata.newBuilder()
+ .setKey("repoRootPath")
+ .setValue(repoRootPathForGoldens))
+
+ if (comparisonStatistics != null) {
+ resultProto.comparisonStatistics = comparisonStatistics
+ }
+ resultProto.imageLocationGolden =
+ if (pathToGoldensInRepo.isEmpty()) {
+ goldenIdentifierResolver(goldenIdentifier)
+ } else {
+ "$pathToGoldensInRepo/${goldenIdentifierResolver(goldenIdentifier)}"
+ }
+
+ val report = Bundle()
+
+ actual.writeToDevice(OutputFileType.IMAGE_ACTUAL).also {
+ resultProto.imageLocationTest = it.name
+ report.putString(bundleKeyPrefix + OutputFileType.IMAGE_ACTUAL, it.absolutePath)
+ }
+ diff?.run {
+ writeToDevice(OutputFileType.IMAGE_DIFF).also {
+ resultProto.imageLocationDiff = it.name
+ report.putString(bundleKeyPrefix + OutputFileType.IMAGE_DIFF, it.absolutePath)
+ }
+ }
+ expected?.run {
+ writeToDevice(OutputFileType.IMAGE_EXPECTED).also {
+ resultProto.imageLocationReference = it.name
+ report.putString(
+ bundleKeyPrefix + OutputFileType.IMAGE_EXPECTED,
+ it.absolutePath
+ )
+ }
+ }
+
+ writeToDevice(OutputFileType.RESULT_PROTO) {
+ it.write(resultProto.build().toString().toByteArray())
+ }.also {
+ report.putString(bundleKeyPrefix + OutputFileType.RESULT_PROTO, it.absolutePath)
+ }
+
+ writeToDevice(OutputFileType.RESULT_BIN_PROTO) {
+ it.write(resultProto.build().toByteArray())
+ }.also {
+ report.putString(bundleKeyPrefix + OutputFileType.RESULT_BIN_PROTO, it.absolutePath)
+ }
+
+ InstrumentationRegistry.getInstrumentation().sendStatus(bundleStatusInProgress, report)
+ }
+
+ internal fun getPathOnDeviceFor(fileType: OutputFileType): File {
+ val fileName = when (fileType) {
+ OutputFileType.IMAGE_ACTUAL -> "${testIdentifier}_actual$imageExtension"
+ OutputFileType.IMAGE_EXPECTED -> "${testIdentifier}_expected$imageExtension"
+ OutputFileType.IMAGE_DIFF -> "${testIdentifier}_diff$imageExtension"
+ OutputFileType.RESULT_PROTO -> "${testIdentifier}_$resultProtoFileSuffix"
+ OutputFileType.RESULT_BIN_PROTO -> "${testIdentifier}_$resultBinaryProtoFileSuffix"
+ }
+ return File(deviceOutputDirectory, fileName)
+ }
+
+ private fun Bitmap.writeToDevice(fileType: OutputFileType): File {
+ return writeToDevice(fileType) {
+ compress(Bitmap.CompressFormat.PNG, 0 /*ignored for png*/, it)
+ }
+ }
+
+ private fun writeToDevice(
+ fileType: OutputFileType,
+ writeAction: (FileOutputStream) -> Unit
+ ): File {
+ if (!deviceOutputDirectory.exists() && !deviceOutputDirectory.mkdir()) {
+ throw IOException("Could not create folder.")
+ }
+
+ var file = getPathOnDeviceFor(fileType)
+ try {
+ FileOutputStream(file).use {
+ writeAction(it)
+ }
+ } catch (e: Exception) {
+ throw IOException(
+ "Could not write file to storage (path: ${file.absolutePath}). " +
+ " Stacktrace: " + e.stackTrace
+ )
+ }
+ return file
+ }
+
+ private fun getDeviceModel(): String {
+ var model = android.os.Build.MODEL.lowercase()
+ arrayOf("phone", "x86", "x64", "gms").forEach {
+ model = model.replace(it, "")
+ }
+ return model.trim().replace(" ", "_")
+ }
+}
+
+internal fun Bitmap.toIntArray(): IntArray {
+ val bitmapArray = IntArray(width * height)
+ getPixels(bitmapArray, 0, width, 0, 0, width, height)
+ return bitmapArray
+}
+
+/**
+ * Asserts this bitmap against the golden identified by the given name.
+ *
+ * Note: The golden identifier should be unique per your test module (unless you want multiple tests
+ * to match the same golden). The name must not contain extension. You should also avoid adding
+ * strings like "golden", "image" and instead describe what is the golder referring to.
+ *
+ * @param rule The screenshot test rule that provides the comparison and reporting.
+ * @param goldenIdentifier Name of the golden. Allowed characters: 'A-Za-z0-9_-'
+ * @param matcher The algorithm to be used to perform the matching. By default [MSSIMMatcher]
+ * is used.
+ *
+ * @see MSSIMMatcher
+ * @see PixelPerfectMatcher
+ */
+fun Bitmap.assertAgainstGolden(
+ rule: ScreenshotTestRule,
+ goldenIdentifier: String,
+ matcher: BitmapMatcher = MSSIMMatcher()
+) {
+ rule.assertBitmapAgainstGolden(this, goldenIdentifier, matcher = matcher)
+}
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/BitmapMatcher.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/BitmapMatcher.kt
new file mode 100644
index 000000000..898276a24
--- /dev/null
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/BitmapMatcher.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot.matchers
+
+import android.graphics.Bitmap
+import platform.test.screenshot.proto.ScreenshotResultProto.DiffResult.ComparisonStatistics
+
+/**
+ * Interface to implement to provide custom bitmap matchers.
+ */
+interface BitmapMatcher {
+ /**
+ * Compares the given bitmaps and returns result of the operation.
+ *
+ * The images need to have same size.
+ *
+ * @param expected The reference / golden image.
+ * @param given The image taken during the test.
+ * @param width Width of both of the images.
+ * @param height Height of both of the images.
+ */
+ fun compareBitmaps(expected: IntArray, given: IntArray, width: Int, height: Int): MatchResult
+}
+
+/**
+ * Result of the matching performed by [BitmapMatcher].
+ *
+ * @param matches True if bitmaps match.
+ * @param comparisonStatistics Matching statistics provided by this matcher that performed the
+ * comparison.
+ * @param diff Diff bitmap that highlights the differences between the images. Can be null if match
+ * was found.
+ */
+class MatchResult(
+ val matches: Boolean,
+ val comparisonStatistics: ComparisonStatistics,
+ val diff: Bitmap?
+)
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/MSSIMMatcher.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/MSSIMMatcher.kt
new file mode 100644
index 000000000..d3d032463
--- /dev/null
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/MSSIMMatcher.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot.matchers
+
+import android.graphics.Color
+import androidx.annotation.FloatRange
+import kotlin.math.pow
+import platform.test.screenshot.proto.ScreenshotResultProto
+
+/**
+ * Image comparison using Structural Similarity Index, developed by Wang, Bovik, Sheikh, and
+ * Simoncelli. Details can be read in their paper:
+ * https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf
+ */
+class MSSIMMatcher(
+ @FloatRange(from = 0.0, to = 1.0) private val threshold: Double = 0.98
+) : BitmapMatcher {
+
+ companion object {
+ // These values were taken from the publication
+ private const val CONSTANT_L = 254.0
+ private const val CONSTANT_K1 = 0.00001
+ private const val CONSTANT_K2 = 0.00003
+ private val CONSTANT_C1 = (CONSTANT_L * CONSTANT_K1).pow(2.0)
+ private val CONSTANT_C2 = (CONSTANT_L * CONSTANT_K2).pow(2.0)
+ private const val WINDOW_SIZE = 10
+ }
+
+ override fun compareBitmaps(
+ expected: IntArray,
+ given: IntArray,
+ width: Int,
+ height: Int
+ ): MatchResult {
+ val calSSIMResult = calculateSSIM(expected, given, width, height)
+
+ val stats = ScreenshotResultProto.DiffResult.ComparisonStatistics
+ .newBuilder()
+ .setNumberPixelsCompared(calSSIMResult.numPixelsCompared)
+ .setNumberPixelsSimilar(calSSIMResult.numPixelsSimilar)
+ .setNumberPixelsIgnored(calSSIMResult.numPixelsIgnored)
+ .setNumberPixelsDifferent(
+ calSSIMResult.numPixelsCompared - calSSIMResult.numPixelsSimilar)
+ .build()
+
+ if (calSSIMResult.numPixelsSimilar
+ >= threshold * calSSIMResult.numPixelsCompared.toDouble()) {
+ return MatchResult(
+ matches = true,
+ diff = null,
+ comparisonStatistics = stats
+ )
+ }
+
+ // Create diff
+ val result = PixelPerfectMatcher()
+ .compareBitmaps(expected, given, width, height)
+ return MatchResult(
+ matches = false,
+ diff = result.diff,
+ comparisonStatistics = stats
+ )
+ }
+
+ internal fun calculateSSIM(
+ ideal: IntArray,
+ given: IntArray,
+ width: Int,
+ height: Int
+ ): SSIMResult {
+ return calculateSSIM(ideal, given, 0, width, width, height)
+ }
+
+ private fun calculateSSIM(
+ ideal: IntArray,
+ given: IntArray,
+ offset: Int,
+ stride: Int,
+ width: Int,
+ height: Int
+ ): SSIMResult {
+ var SSIMTotal = 0.0
+ var windows = 0
+ var currentWindowY = 0
+ var ignored = 0
+
+ while (currentWindowY < height) {
+ val windowHeight = computeWindowSize(currentWindowY, height)
+ var currentWindowX = 0
+ while (currentWindowX < width) {
+ val windowWidth = computeWindowSize(currentWindowX, width)
+ val start: Int =
+ indexFromXAndY(currentWindowX, currentWindowY, stride, offset)
+ if (isWindowWhite(ideal, start, stride, windowWidth, windowHeight) &&
+ isWindowWhite(given, start, stride, windowWidth, windowHeight)
+ ) {
+ currentWindowX += WINDOW_SIZE
+ ignored += WINDOW_SIZE
+ continue
+ }
+ windows++
+ val means =
+ getMeans(ideal, given, start, stride, windowWidth, windowHeight)
+ val meanX = means[0]
+ val meanY = means[1]
+ val variances = getVariances(
+ ideal, given, meanX, meanY, start, stride,
+ windowWidth, windowHeight
+ )
+ val varX = variances[0]
+ val varY = variances[1]
+ val stdBoth = variances[2]
+ val SSIM = SSIM(meanX, meanY, varX, varY, stdBoth)
+ SSIMTotal += SSIM
+ currentWindowX += WINDOW_SIZE
+ }
+ currentWindowY += WINDOW_SIZE
+ }
+ return SSIMResult(
+ SSIM = SSIMTotal,
+ numPixelsSimilar = (SSIMTotal + 0.5).toInt(),
+ numPixelsIgnored = ignored,
+ numPixelsCompared = windows
+ )
+ }
+
+ /**
+ * Compute the size of the window. The window defaults to WINDOW_SIZE, but
+ * must be contained within dimension.
+ */
+ private fun computeWindowSize(coordinateStart: Int, dimension: Int): Int {
+ return if (coordinateStart + WINDOW_SIZE <= dimension) {
+ WINDOW_SIZE
+ } else {
+ dimension - coordinateStart
+ }
+ }
+
+ private fun isWindowWhite(
+ colors: IntArray,
+ start: Int,
+ stride: Int,
+ windowWidth: Int,
+ windowHeight: Int
+ ): Boolean {
+ for (y in 0 until windowHeight) {
+ for (x in 0 until windowWidth) {
+ if (colors[indexFromXAndY(x, y, stride, start)] != Color.WHITE) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ /**
+ * This calculates the position in an array that would represent a bitmap given the parameters.
+ */
+ private fun indexFromXAndY(x: Int, y: Int, stride: Int, offset: Int): Int {
+ return x + y * stride + offset
+ }
+
+ private fun SSIM(muX: Double, muY: Double, sigX: Double, sigY: Double, sigXY: Double): Double {
+ var SSIM = (2 * muX * muY + CONSTANT_C1) * (2 * sigXY + CONSTANT_C2)
+ val denom = ((muX * muX + muY * muY + CONSTANT_C1) * (sigX + sigY + CONSTANT_C2))
+ SSIM /= denom
+ return SSIM
+ }
+
+ /**
+ * This method will find the mean of a window in both sets of pixels. The return is an array
+ * where the first double is the mean of the first set and the second double is the mean of the
+ * second set.
+ */
+ private fun getMeans(
+ pixels0: IntArray,
+ pixels1: IntArray,
+ start: Int,
+ stride: Int,
+ windowWidth: Int,
+ windowHeight: Int
+ ): DoubleArray {
+ var avg0 = 0.0
+ var avg1 = 0.0
+ for (y in 0 until windowHeight) {
+ for (x in 0 until windowWidth) {
+ val index: Int = indexFromXAndY(x, y, stride, start)
+ avg0 += getIntensity(pixels0[index])
+ avg1 += getIntensity(pixels1[index])
+ }
+ }
+ avg0 /= windowWidth * windowHeight.toDouble()
+ avg1 /= windowWidth * windowHeight.toDouble()
+ return doubleArrayOf(avg0, avg1)
+ }
+
+ /**
+ * Finds the variance of the two sets of pixels, as well as the covariance of the windows. The
+ * return value is an array of doubles, the first is the variance of the first set of pixels,
+ * the second is the variance of the second set of pixels, and the third is the covariance.
+ */
+ private fun getVariances(
+ pixels0: IntArray,
+ pixels1: IntArray,
+ mean0: Double,
+ mean1: Double,
+ start: Int,
+ stride: Int,
+ windowWidth: Int,
+ windowHeight: Int
+ ): DoubleArray {
+ var var0 = 0.0
+ var var1 = 0.0
+ var varBoth = 0.0
+ for (y in 0 until windowHeight) {
+ for (x in 0 until windowWidth) {
+ val index: Int = indexFromXAndY(x, y, stride, start)
+ val v0 = getIntensity(pixels0[index]) - mean0
+ val v1 = getIntensity(pixels1[index]) - mean1
+ var0 += v0 * v0
+ var1 += v1 * v1
+ varBoth += v0 * v1
+ }
+ }
+ var0 /= windowWidth * windowHeight - 1.toDouble()
+ var1 /= windowWidth * windowHeight - 1.toDouble()
+ varBoth /= windowWidth * windowHeight - 1.toDouble()
+ return doubleArrayOf(var0, var1, varBoth)
+ }
+
+ /**
+ * Gets the intensity of a given pixel in RGB using luminosity formula
+ *
+ * l = 0.21R' + 0.72G' + 0.07B'
+ *
+ * The prime symbols dictate a gamma correction of 1.
+ */
+ private fun getIntensity(pixel: Int): Double {
+ val gamma = 1.0
+ var l = 0.0
+ l += 0.21f * (Color.red(pixel) / 255f.toDouble()).pow(gamma)
+ l += 0.72f * (Color.green(pixel) / 255f.toDouble()).pow(gamma)
+ l += 0.07f * (Color.blue(pixel) / 255f.toDouble()).pow(gamma)
+ return l
+ }
+}
+
+/**
+ * Result of the calculation of SSIM.
+ *
+ * @param numPixelsSimilar The number of similar pixels.
+ * @param numPixelsIgnored The number of ignored pixels.
+ * @param numPixelsCompared The number of compared pixels.
+ */
+class SSIMResult(
+ val SSIM: Double,
+ val numPixelsSimilar: Int,
+ val numPixelsIgnored: Int,
+ val numPixelsCompared: Int
+)
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/PixelPerfectMatcher.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/PixelPerfectMatcher.kt
new file mode 100644
index 000000000..e1e487565
--- /dev/null
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/matchers/PixelPerfectMatcher.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package platform.test.screenshot.matchers
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import platform.test.screenshot.proto.ScreenshotResultProto
+
+/**
+ * Bitmap matching that does an exact comparison of pixels between bitmaps.
+ */
+class PixelPerfectMatcher : BitmapMatcher {
+
+ override fun compareBitmaps(
+ expected: IntArray,
+ given: IntArray,
+ width: Int,
+ height: Int
+ ): MatchResult {
+ check(expected.size == given.size)
+
+ var different = 0
+ var same = 0
+
+ val diffArray = IntArray(width * height)
+
+ for (x in 0 until width) {
+ for (y in 0 until height) {
+ val index = x + y * width
+ val referenceColor = expected[index]
+ val testColor = given[index]
+ if (referenceColor == testColor) {
+ ++same
+ } else {
+ ++different
+ }
+ diffArray[index] =
+ diffColor(
+ referenceColor,
+ testColor
+ )
+ }
+ }
+
+ val stats = ScreenshotResultProto.DiffResult.ComparisonStatistics
+ .newBuilder()
+ .setNumberPixelsCompared(width * height)
+ .setNumberPixelsIdentical(same)
+ .setNumberPixelsDifferent(different)
+ .build()
+
+ if (different > 0) {
+ val diff = Bitmap.createBitmap(diffArray, width, height, Bitmap.Config.ARGB_8888)
+ return MatchResult(matches = false, diff = diff, comparisonStatistics = stats)
+ }
+ return MatchResult(matches = true, diff = null, comparisonStatistics = stats)
+ }
+
+ private fun diffColor(referenceColor: Int, testColor: Int): Int {
+ return if (referenceColor != testColor) {
+ Color.MAGENTA
+ } else {
+ Color.TRANSPARENT
+ }
+ }
+}
diff --git a/tests/automotive/functional/home/Android.bp b/tests/automotive/functional/home/Android.bp
index 19f5b9b3f..665953897 100644
--- a/tests/automotive/functional/home/Android.bp
+++ b/tests/automotive/functional/home/Android.bp
@@ -30,5 +30,5 @@ android_test {
"platform-test-options",
],
srcs: ["src/**/*.java"],
- test_suites: ["catbox"],
+ test_suites: ["catbox","general-tests"],
}
diff --git a/tests/automotive/functional/mediacenter/src/android/platform/tests/MediaTestAppTest.java b/tests/automotive/functional/mediacenter/src/android/platform/tests/MediaTestAppTest.java
new file mode 100644
index 000000000..cc73ff2ff
--- /dev/null
+++ b/tests/automotive/functional/mediacenter/src/android/platform/tests/MediaTestAppTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.tests;
+
+import static junit.framework.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.platform.helpers.AutoUtility;
+import android.platform.helpers.IAutoHomeHelper;
+import android.platform.helpers.IAutoMediaHelper;
+import android.platform.helpers.HelperAccessor;
+import android.platform.helpers.IAutoTestMediaAppHelper;
+import android.platform.test.option.StringOption;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaTestAppTest {
+ private static final String MEDIA_APP = "media-app";
+ private static final String TEST_MEDIA_APP = "Test Media App";
+
+ @ClassRule
+ public static StringOption mMediaTestApp =
+ new StringOption(MEDIA_APP).setRequired(false);
+
+ private static HelperAccessor<IAutoMediaHelper> sMediaCenterHelper =
+ new HelperAccessor<>(IAutoMediaHelper.class);
+ private static HelperAccessor<IAutoTestMediaAppHelper> sTestMediaAppHelper =
+ new HelperAccessor<>(IAutoTestMediaAppHelper.class);
+ private static HelperAccessor<IAutoHomeHelper> sAutoHomeHelper =
+ new HelperAccessor<>(IAutoHomeHelper.class);
+
+ @BeforeClass
+ public static void exitSuw() {
+ AutoUtility.exitSuw();
+ // Load songs on Test Media App
+ sAutoHomeHelper.get().openMediaWidget();
+ sMediaCenterHelper.get().openMediaAppMenuItems();
+ String mediaAppName = TEST_MEDIA_APP;
+ if (mMediaTestApp != null
+ && mMediaTestApp.get() != null && !mMediaTestApp.get().isEmpty()) {
+ mediaAppName = mMediaTestApp.get();
+ }
+ sMediaCenterHelper.get().openApp(mediaAppName);
+ assertTrue("Not a media app",
+ sMediaCenterHelper.get().getMediaAppTitle().equals(mediaAppName));
+ sMediaCenterHelper.get().openMediaAppSettingsPage();
+ sTestMediaAppHelper.get().loadMediaInLocalMediaTestApp();
+ }
+
+ @Test
+ public void testPlayPauseMedia() {
+ sMediaCenterHelper.get().playMedia();
+ assertTrue("Song not playing.", sMediaCenterHelper.get().isPlaying());
+ sMediaCenterHelper.get().pauseMedia();
+ assertFalse("Song not paused.", sMediaCenterHelper.get().isPlaying());
+ }
+
+ @Test
+ public void testNextTrack() {
+ String currentSong = sMediaCenterHelper.get().getMediaTrackName();
+ sMediaCenterHelper.get().clickNextTrack();
+ assertNotEquals(
+ "Song playing has not been changed",
+ currentSong,
+ sMediaCenterHelper.get().getMediaTrackName());
+ }
+}
diff --git a/tests/automotive/functional/mediacenter/src/android/platform/tests/NoUserLoggedInTest.java b/tests/automotive/functional/mediacenter/src/android/platform/tests/NoUserLoggedInTest.java
new file mode 100644
index 000000000..af58d4049
--- /dev/null
+++ b/tests/automotive/functional/mediacenter/src/android/platform/tests/NoUserLoggedInTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.tests;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.platform.helpers.AutoUtility;
+import android.platform.helpers.HelperAccessor;
+import android.platform.helpers.IAutoHomeHelper;
+import android.platform.helpers.IAutoMediaHelper;
+import android.platform.test.option.StringOption;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NoUserLoggedInTest {
+
+ @ClassRule
+ public static StringOption mMediaApp =
+ new StringOption("media-app").setRequired(false).setDefault("YouTube Music");
+ private HelperAccessor<IAutoHomeHelper> mAutoHomeHelper;
+ private HelperAccessor<IAutoMediaHelper> mMediaCenterHelper;
+
+ public NoUserLoggedInTest() throws Exception {
+ mMediaCenterHelper = new HelperAccessor<>(IAutoMediaHelper.class);
+ mAutoHomeHelper = new HelperAccessor<>(IAutoHomeHelper.class);
+ }
+
+ @BeforeClass
+ public static void exitSuw() {
+ AutoUtility.exitSuw();
+ }
+
+ @Test
+ public void testNoUserLogInMessage() {
+ mAutoHomeHelper.get().openMediaWidget();
+ mMediaCenterHelper.get().openMediaAppMenuItems();
+ mMediaCenterHelper.get().openApp(mMediaApp.get());
+
+ assertTrue("Not a media app.",
+ mMediaCenterHelper.get().getMediaAppTitle().equals(mMediaApp.get()));
+
+ String noUserLoginMsg = mMediaCenterHelper.get().getMediaAppUserNotLoggedInErrorMessage();
+ assertTrue("Incorrect Sign in error message.",
+ noUserLoginMsg.equals("Please sign in to YouTube Music."));
+ }
+}
diff --git a/tests/automotive/functional/notifications/Android.bp b/tests/automotive/functional/notifications/Android.bp
index c962633bf..0fe19d444 100644
--- a/tests/automotive/functional/notifications/Android.bp
+++ b/tests/automotive/functional/notifications/Android.bp
@@ -29,5 +29,5 @@ android_test {
"hamcrest-library",
],
srcs: ["src/**/*.java"],
- test_suites: ["catbox"],
+ test_suites: ["catbox","general-tests"],
}
diff --git a/tests/automotive/functional/settings/Android.bp b/tests/automotive/functional/settings/Android.bp
index 6f575c5a8..47322f5f3 100644
--- a/tests/automotive/functional/settings/Android.bp
+++ b/tests/automotive/functional/settings/Android.bp
@@ -30,5 +30,5 @@ android_test {
"platform-test-options",
],
srcs: ["src/**/*.java"],
- test_suites: ["catbox"],
+ test_suites: ["catbox","general-tests"],
}
diff --git a/tests/automotive/health/settings/src/android/platform/scenario/settings/ScrollInApp.java b/tests/automotive/health/settings/src/android/platform/scenario/settings/ScrollInApp.java
index 054e8b453..1350af3a3 100644
--- a/tests/automotive/health/settings/src/android/platform/scenario/settings/ScrollInApp.java
+++ b/tests/automotive/health/settings/src/android/platform/scenario/settings/ScrollInApp.java
@@ -33,7 +33,7 @@ public class ScrollInApp {
@Test
public void testScrollDownAndUp() {
- sHelper.get().scrollDownOnePage(500);
- sHelper.get().scrollUpOnePage(500);
+ sHelper.get().scrollDownOnePage();
+ sHelper.get().scrollUpOnePage();
}
}
diff --git a/tests/bootdoa/Android.mk b/tests/bootdoa/Android.mk
index ff86b929c..28a9c74d9 100644
--- a/tests/bootdoa/Android.mk
+++ b/tests/bootdoa/Android.mk
@@ -15,3 +15,4 @@
LOCAL_PATH:= $(call my-dir)
$(call dist-for-goals, droidcore, $(LOCAL_PATH)/fatal_allowlist)
+$(call declare-1p-target,$(LOCAL_PATH)/fatal_allowlist,platform_testing)
diff --git a/tests/codecoverage/native/cpp/Android.bp b/tests/codecoverage/native/cpp/Android.bp
new file mode 100644
index 000000000..5f0817722
--- /dev/null
+++ b/tests/codecoverage/native/cpp/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "CoverageCppSmokeTest",
+
+ srcs: ["coverage_cpp_smoke_test.cpp"],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+}
diff --git a/tests/codecoverage/native/cpp/coverage_cpp_smoke_test.cpp b/tests/codecoverage/native/cpp/coverage_cpp_smoke_test.cpp
new file mode 100644
index 000000000..2aaf18acc
--- /dev/null
+++ b/tests/codecoverage/native/cpp/coverage_cpp_smoke_test.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+void not_covered() {
+ return;
+}
+
+void covered() {
+ return;
+}
+
+TEST(cpp_smoke, call_covered) {
+ covered();
+}
diff --git a/tests/codecoverage/native/rust/Android.bp b/tests/codecoverage/native/rust/Android.bp
new file mode 100644
index 000000000..94d78a670
--- /dev/null
+++ b/tests/codecoverage/native/rust/Android.bp
@@ -0,0 +1,19 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_test {
+ name: "CoverageRustSmokeTest",
+ srcs: [
+ "coverage_rust_smoke_test.rs",
+ ],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+}
diff --git a/tests/codecoverage/native/rust/coverage_rust_smoke_test.rs b/tests/codecoverage/native/rust/coverage_rust_smoke_test.rs
new file mode 100644
index 000000000..8b7ec56fb
--- /dev/null
+++ b/tests/codecoverage/native/rust/coverage_rust_smoke_test.rs
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#[allow(dead_code)]
+fn not_covered() {
+}
+
+fn covered() {
+}
+
+#[test]
+fn test() {
+ covered();
+}
diff --git a/tests/functional/devicehealthchecks/assets/bug_map b/tests/functional/devicehealthchecks/assets/bug_map
index fe49257bb..9cbe0576a 100644
--- a/tests/functional/devicehealthchecks/assets/bug_map
+++ b/tests/functional/devicehealthchecks/assets/bug_map
@@ -1,12 +1,14 @@
<test_name> <regex.no.spaces> <only_bug_number>
system_app_anr com.google.android.apps.wellbeing*.*ContextManagerRestartBroadcastReceiver_Receiver[\s\S]*cf_x86_phone 166183732
system_app_anr com.google.android.apps.dreamliner/.dnd.DockConditionProviderService 166174264
+system_app_anr com.google.android.gms[\s\S]*executing\sservice\scom.google.android.gms/.nearby.sharing.SharingTileService 193719277
system_app_anr act=android.hardware.usb.action.USB_STATE[\S\s]*cmp=com.google.android.projection.gearhead[\s\S]*ConnectivityEventHandlerImpl\$ConnectivityEventBroadcastReceiver 130956983
-system_app_anr com.google.android.euicc[\s\S]*executing\sservice\scom.google.android.euicc/com.android.euicc.service.EuiccServiceImpl 174479972
system_app_anr com.google.android.apps.wellbeing*.*ContextManagerRestartBroadcastReceiver_Receiver[\s\S]*cf_x86_64_phone 166183732
system_app_anr executing\sservice\scom.google.android.as/com.google.android.apps.miphone.aiai.echo.notificationintelligence.scheduler.impl.NotificationJobService 192300119
system_app_anr executing\sservice\scom.google.android.as/com.google.android.apps.miphone.aiai.echo.scheduler.EchoJobService 192300119
system_app_anr executing\sservice\scom.google.android.as/com.google.android.apps.miphone.aiai.actions.service.ActionRankingDataTtlService 192300119
+system_app_anr executing\sservice\scom.android.se/.SecureElementService 199457346
+system_app_anr act=android.telephony.action.CARRIER_CONFIG_CHANGED\sflg=0x15000010\scmp=com.android.phone/.otasp.OtaspSimStateReceiver 205896452
system_app_crash -1\|android\|26\|null\|1000 155073214
system_app_crash -1\|android\|32\|null\|1000 155073214
system_app_crash android.database.sqlite.SQLiteCloseable.acquireReference 159658068
@@ -20,10 +22,12 @@ system_app_native_crash ReferenceQueueD*.*com.google.android.apps.safetyhub 1621
system_app_native_crash Binder*.*com.google.android.apps.safetyhub 162104694
system_app_native_crash Lite\sThread*.*com.google.android.apps.safetyhub 162379378
system_app_native_crash BG\sThread*.*com.google.android.apps.safetyhub 162381002
+system_app_native_crash FireflyProcMgr\s+>>>\scom.google.android.GoogleCamera\s<<<[\S\s]+SIGSEGV[\S\s]+null\spointer\sdereference 195519497
SYSTEM_TOMBSTONE Binder*.*com.google.android.apps.safetyhub 162104694
SYSTEM_TOMBSTONE ReferenceQueueD*.*com.google.android.apps.safetyhub 162103095
SYSTEM_TOMBSTONE Lite\sThread*.*com.google.android.apps.safetyhub 162379378
SYSTEM_TOMBSTONE BG\sThread*.*com.google.android.apps.safetyhub 162381002
+SYSTEM_TOMBSTONE FireflyProcMgr\s+>>>\scom.google.android.GoogleCamera\s<<<[\S\s]+SIGSEGV[\S\s]+null\spointer\sdereference 195519497
SYSTEM_TOMBSTONE AsyncTask\s+#1\s+>>>\s+com.android.nfc\s+<<<[\S\s]*nfaDeviceManagementCallback[\S\s]*nfc_ncif_cmd_timeout 172057778
SYSTEM_TOMBSTONE >>>\s+/apex/com.android.os.statsd/bin/statsd\s+<<<[\S\s]*HandleUsingDestroyedMutex 172829930
SYSTEM_TOMBSTONE audio.service\s+>>>\s+/vendor/bin/hw/android.hardware.audio.service\s+<<<[\S\s]*debuggerd\ssignal[\S\s]*audio_extn_utils_get_snd_card_num 174265816
diff --git a/tests/functional/devicehealthchecks/src/com/android/devicehealthchecks/CrashCheckBase.java b/tests/functional/devicehealthchecks/src/com/android/devicehealthchecks/CrashCheckBase.java
index 191e9b885..23301a75b 100644
--- a/tests/functional/devicehealthchecks/src/com/android/devicehealthchecks/CrashCheckBase.java
+++ b/tests/functional/devicehealthchecks/src/com/android/devicehealthchecks/CrashCheckBase.java
@@ -115,6 +115,7 @@ abstract class CrashCheckBase {
if (matcher.find()) {
errorDetails.append(line);
if (scanner.hasNextLine()) {
+ errorDetails.append("\n");
errorDetails.append(scanner.nextLine());
}
break;
diff --git a/tests/health/scenarios/src/android/platform/test/scenario/annotation/LargeScreenOnly.java b/tests/health/scenarios/src/android/platform/test/scenario/annotation/LargeScreenOnly.java
new file mode 100644
index 000000000..3711d3cf3
--- /dev/null
+++ b/tests/health/scenarios/src/android/platform/test/scenario/annotation/LargeScreenOnly.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.scenario.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Identifies scenario that should be run only on large screen devices. Note that this annotation
+ * doesn't do any filtering for screen size, it's up to the test author to annotate the test
+ * correctly.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LargeScreenOnly {}