summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp34
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/multiuser/Android.bp7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java12
-rw-r--r--api/Android.bp48
-rw-r--r--api/StubLibraries.bp1
-rw-r--r--api/api.go44
-rw-r--r--api/api_test.go254
-rw-r--r--api/go.work6
-rw-r--r--core/api/current.txt7
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/animation/AnimatorSet.java12
-rw-r--r--core/java/android/animation/OWNERS1
-rw-r--r--core/java/android/app/ActivityThread.java3
-rw-r--r--core/java/android/app/PictureInPictureParams.java27
-rw-r--r--core/java/android/app/SystemServiceRegistry.java5
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java54
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/app/servertransaction/ObjectPool.java67
-rw-r--r--core/java/android/app/servertransaction/ObjectPoolItem.java8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig10
-rw-r--r--core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig7
-rw-r--r--core/java/android/os/AggregateBatteryConsumer.java2
-rw-r--r--core/java/android/os/BatteryConsumer.java445
-rw-r--r--core/java/android/os/BatteryUsageStats.java140
-rw-r--r--core/java/android/os/BatteryUsageStatsQuery.java30
-rw-r--r--core/java/android/os/IBinder.java37
-rw-r--r--core/java/android/os/INetworkManagementService.aidl86
-rw-r--r--core/java/android/os/PowerComponents.java404
-rw-r--r--core/java/android/os/ServiceManager.java4
-rw-r--r--core/java/android/os/ServiceManagerNative.java6
-rw-r--r--core/java/android/os/TEST_MAPPING2
-rw-r--r--core/java/android/os/UidBatteryConsumer.java50
-rw-r--r--core/java/android/os/UserBatteryConsumer.java8
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/service/dreams/DreamService.java5
-rw-r--r--core/java/android/service/dreams/utils/DreamAccessibility.java44
-rw-r--r--core/java/android/text/ClientFlags.java7
-rw-r--r--core/java/android/text/TextFlags.java2
-rw-r--r--core/java/android/text/flags/flags.aconfig20
-rw-r--r--core/java/android/text/format/DateIntervalFormat.java11
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java18
-rw-r--r--core/java/android/view/InsetsController.java2
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java16
-rw-r--r--core/java/android/view/SurfaceView.java9
-rw-r--r--core/java/android/view/ViewRootImpl.java40
-rw-r--r--core/java/android/view/WindowInsets.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java173
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig7
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java33
-rw-r--r--core/java/android/window/SnapshotDrawerUtils.java8
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig7
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig10
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig10
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java44
-rw-r--r--core/java/com/android/internal/jank/Cuj.java35
-rw-r--r--core/java/com/android/internal/os/PowerStats.java7
-rw-r--r--core/java/com/android/internal/os/TEST_MAPPING2
-rw-r--r--core/java/com/android/internal/policy/FoldLockSettingsObserver.java128
-rw-r--r--core/java/com/android/internal/protolog/IProtoLogClient.aidl29
-rw-r--r--core/java/com/android/internal/protolog/IProtoLogService.aidl51
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java46
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogDataSource.java30
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java11
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java3
-rw-r--r--core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java2
-rw-r--r--core/jni/android_media_AudioSystem.cpp72
-rw-r--r--core/jni/android_view_MotionEvent.cpp2
-rw-r--r--core/jni/platform/host/HostRuntime.cpp17
-rw-r--r--core/proto/android/server/vibrator/vibratormanagerservice.proto1
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java6
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java4
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java2
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java2
-rw-r--r--core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp30
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java39
-rw-r--r--core/tests/coretests/src/android/animation/OWNERS1
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java8
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java8
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java6
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java57
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java9
-rw-r--r--core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java2
-rw-r--r--core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java9
-rw-r--r--core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java12
-rw-r--r--core/tests/coretests/src/android/view/ViewFrameRateTest.java4
-rw-r--r--core/tests/coretests/src/android/view/WindowInfoTest.java2
-rw-r--r--core/tests/coretests/src/android/view/WindowInsetsTest.java18
-rw-r--r--core/tests/coretests/src/android/view/WindowManagerTests.java2
-rw-r--r--core/tests/coretests/src/android/view/WindowMetricsTest.java2
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java2
-rw-r--r--core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java13
-rw-r--r--core/tests/coretests/src/android/window/TaskFpsCallbackTest.java2
-rw-r--r--core/tests/coretests/src/android/window/WindowContextControllerTest.java11
-rw-r--r--core/tests/coretests/src/android/window/WindowContextTest.java2
-rw-r--r--core/tests/coretests/src/android/window/WindowMetricsHelperTest.java2
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java12
-rw-r--r--core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java8
-rw-r--r--core/tests/coretests/src/android/window/flags/WindowFlagsTest.java2
-rw-r--r--core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java110
-rw-r--r--graphics/java/android/graphics/Paint.java29
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java33
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java12
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java5
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java122
-rw-r--r--libs/WindowManager/Shell/Android.bp3
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java173
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java112
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt70
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java134
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt159
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt109
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt4
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp1
-rw-r--r--libs/hwui/tests/common/TestUtils.h15
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp5
-rw-r--r--libs/hwui/tests/unit/SkiaDisplayListTests.cpp4
-rw-r--r--libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp14
-rw-r--r--location/java/android/location/flags/location.aconfig14
-rw-r--r--media/java/android/media/AudioSystem.java7
-rw-r--r--media/java/android/media/projection/MediaProjection.java16
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt32
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt22
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt13
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip (renamed from packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip)bin138039528 -> 136114148 bytes
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jarbin43453 -> 43504 bytes
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties2
-rwxr-xr-xpackages/SettingsLib/Spa/gradlew5
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp3
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java46
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java74
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java9
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SystemUI/aconfig/communal.aconfig9
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt13
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt56
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt71
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt41
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt102
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt685
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt)385
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt510
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt33
-rw-r--r--packages/SystemUI/res/drawable/checkbox_circle_shape.xml (renamed from core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml)30
-rw-r--r--packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml27
-rw-r--r--packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml27
-rw-r--r--packages/SystemUI/res/layout/app_clips_screenshot.xml3
-rw-r--r--packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml20
-rw-r--r--packages/SystemUI/res/values/flags.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml31
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt293
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationsRepositoryExt.kt)16
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt295
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt129
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt565
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt356
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt239
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt42
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt5
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java15
-rw-r--r--services/autofill/java/com/android/server/autofill/Helper.java48
-rw-r--r--services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java2
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java39
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java16
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java28
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java62
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java105
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java5
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java3
-rw-r--r--services/core/java/com/android/server/app/GameManagerShellCommand.java55
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java66
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java4
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java116
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java4
-rw-r--r--services/core/java/com/android/server/clipboard/ClipboardService.java64
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java19
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java32
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java23
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java20
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java91
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java303
-rw-r--r--services/core/java/com/android/server/display/config/HdrBrightnessData.java41
-rw-r--r--services/core/java/com/android/server/display/config/SensorData.java41
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java9
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java100
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java8
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java5
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java5
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java15
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java3
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java5
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java9
-rw-r--r--services/core/java/com/android/server/net/NetworkManagementService.java173
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java2
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java17
-rw-r--r--services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java12
-rw-r--r--services/core/java/com/android/server/pm/SaferIntentUtils.java6
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java53
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java5
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java5
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java1
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java5
-rw-r--r--services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java9
-rw-r--r--services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java10
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsCollector.java5
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsExporter.java161
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig14
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java1
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java29
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java46
-rw-r--r--services/core/java/com/android/server/webkit/SystemInterface.java36
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateService.java4
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java29
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java31
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java116
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java76
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java10
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java15
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java116
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java300
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java13
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java20
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java28
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java8
-rw-r--r--services/core/java/com/android/server/wm/Session.java19
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java500
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioningController.java250
-rw-r--r--services/core/java/com/android/server/wm/Transition.java25
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java47
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerDebugConfig.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java76
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java38
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowTracing.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowTracingDataSource.java204
-rw-r--r--services/core/java/com/android/server/wm/WindowTracingPerfetto.java163
-rw-r--r--services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java46
-rw-r--r--services/core/jni/com_android_server_UsbDeviceManager.cpp207
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp17
-rw-r--r--services/core/jni/onload.cpp4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java34
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java43
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java47
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java14
-rw-r--r--services/java/com/android/server/SystemServer.java5
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java10
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java5
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java12
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java8
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java12
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java51
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt44
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt405
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt13
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt25
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java6
-rw-r--r--services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java83
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java7
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java (renamed from core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java)267
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java6
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java176
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java12
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/autofill/HelperTest.java113
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java49
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java34
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java4
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java28
-rw-r--r--services/tests/wmtests/res/xml/bookmarks.xml37
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java316
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java89
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java266
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java317
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java526
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java152
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java110
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java68
-rw-r--r--telephony/OWNERS2
-rw-r--r--telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl3
-rw-r--r--tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java2
-rw-r--r--tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java2
-rw-r--r--tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java4
560 files changed, 15440 insertions, 6316 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a16aa2dea25b..7a1add3eb57e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -98,6 +98,7 @@ aconfig_declarations_group {
"framework-jobscheduler-job.flags-aconfig-java",
"framework_graphics_flags_java_lib",
"hwui_flags_java_lib",
+ "libcore_exported_aconfig_flags_lib",
"power_flags_lib",
"sdk_sandbox_flags_lib",
"surfaceflinger_flags_java_lib",
@@ -140,6 +141,14 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Core Libraries / libcore
+java_aconfig_library {
+ name: "libcore_exported_aconfig_flags_lib",
+ aconfig_declarations: "libcore-aconfig-flags",
+ mode: "exported",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Telecom
java_aconfig_library {
name: "telecom_flags_core_java_lib",
@@ -233,6 +242,12 @@ cc_aconfig_library {
aconfig_declarations: "com.android.text.flags-aconfig",
}
+rust_aconfig_library {
+ name: "libandroid_text_flags_rust",
+ crate_name: "android_text_flags",
+ aconfig_declarations: "com.android.text.flags-aconfig",
+}
+
// Location
aconfig_declarations {
name: "android.location.flags-aconfig",
@@ -363,6 +378,7 @@ java_aconfig_library {
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.btservices",
"com.android.mediaprovider",
"com.android.permission",
],
@@ -403,17 +419,6 @@ java_aconfig_library {
cc_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-cc",
aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
-}
-
-cc_aconfig_library {
- name: "android.companion.virtualdevice.flags-aconfig-cc-host",
- aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
- host_supported: true,
-}
-
-cc_aconfig_library {
- name: "android.companion.virtualdevice.flags-aconfig-cc-test",
- aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
host_supported: true,
mode: "test",
}
@@ -1490,6 +1495,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "backstage_power_flags_lib-host",
+ aconfig_declarations: "backstage_power_flags",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Dropbox data
aconfig_declarations {
name: "dropbox_flags",
diff --git a/Android.bp b/Android.bp
index f0aa62cc37ae..eabd9c7565da 100644
--- a/Android.bp
+++ b/Android.bp
@@ -417,7 +417,6 @@ java_defaults {
"modules-utils-fastxmlserializer",
"modules-utils-preconditions",
"modules-utils-statemachine",
- "modules-utils-synchronous-result-receiver",
"modules-utils-os",
"modules-utils-uieventlogger-interface",
"framework-permission-aidl-java",
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index 1653edc77de9..856dba3f804c 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -38,3 +38,10 @@ android_test {
],
certificate: "platform",
}
+
+filegroup {
+ name: "multi_user_trace_config",
+ srcs: [
+ "trace_configs/trace_config_multi_user.textproto",
+ ],
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
index dc5e3414a819..93904a773ed5 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -20,6 +20,7 @@ package com.android.server.alarm;
import android.annotation.Nullable;
import android.os.Environment;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -113,15 +114,18 @@ public class UserWakeupStore {
}
/**
- * Add user wakeup for the alarm.
+ * Add user wakeup for the alarm if needed.
* @param userId Id of the user that scheduled alarm.
* @param alarmTime time when alarm is expected to trigger.
*/
public void addUserWakeup(int userId, long alarmTime) {
- synchronized (mUserWakeupLock) {
- mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
+ // SYSTEM user is always running, so no need to schedule wakeup for it.
+ if (userId != UserHandle.USER_SYSTEM) {
+ synchronized (mUserWakeupLock) {
+ mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
+ }
+ updateUserListFile();
}
- updateUserListFile();
}
/**
diff --git a/api/Android.bp b/api/Android.bp
index 6a04f0d76bf9..d931df165a8f 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -29,12 +29,14 @@ bootstrap_go_package {
pkgPath: "android/soong/api",
deps: [
"blueprint",
+ "blueprint-proptools",
"soong",
"soong-android",
"soong-genrule",
"soong-java",
],
srcs: ["api.go"],
+ testSrcs: ["api_test.go"],
pluginFor: ["soong_build"],
}
@@ -60,40 +62,8 @@ metalava_cmd = "$(location metalava)"
metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
metalava_cmd += " --quiet "
-soong_config_module_type {
- name: "enable_crashrecovery_module",
- module_type: "combined_apis_defaults",
- config_namespace: "ANDROID",
- bool_variables: ["release_crashrecovery_module"],
- properties: [
- "bootclasspath",
- "system_server_classpath",
- ],
-}
-
-soong_config_bool_variable {
- name: "release_crashrecovery_module",
-}
-
-enable_crashrecovery_module {
- name: "crashrecovery_module_defaults",
- soong_config_variables: {
- release_crashrecovery_module: {
- bootclasspath: [
- "framework-crashrecovery",
- ],
- system_server_classpath: [
- "service-crashrecovery",
- ],
- },
- },
-}
-
combined_apis {
name: "frameworks-base-api",
- defaults: [
- "crashrecovery_module_defaults",
- ],
bootclasspath: [
"android.net.ipsec.ike",
"art.module.public.api",
@@ -126,7 +96,12 @@ combined_apis {
"framework-virtualization",
"framework-wifi",
"i18n.module.public.api",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": [
+ "framework-crashrecovery",
+ ],
+ default: [],
+ }),
system_server_classpath: [
"service-art",
"service-configinfrastructure",
@@ -135,7 +110,12 @@ combined_apis {
"service-permission",
"service-rkp",
"service-sdksandbox",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": [
+ "service-crashrecovery",
+ ],
+ default: [],
+ }),
}
genrule {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 12820f9ff277..8dfddf0e13c8 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -1345,4 +1345,5 @@ java_library {
":hwbinder-stubs-docs",
],
visibility: ["//visibility:public"],
+ is_stubs_module: true,
}
diff --git a/api/api.go b/api/api.go
index d4db49e90a01..b6b1a7e44510 100644
--- a/api/api.go
+++ b/api/api.go
@@ -54,16 +54,15 @@ var non_updatable_modules = []string{virtualization, location}
// The properties of the combined_apis module type.
type CombinedApisProperties struct {
// Module libraries in the bootclasspath
- Bootclasspath []string
+ Bootclasspath proptools.Configurable[[]string]
// Module libraries on the bootclasspath if include_nonpublic_framework_api is true.
Conditional_bootclasspath []string
// Module libraries in system server
- System_server_classpath []string
+ System_server_classpath proptools.Configurable[[]string]
}
type CombinedApis struct {
android.ModuleBase
- android.DefaultableModuleBase
properties CombinedApisProperties
}
@@ -74,34 +73,41 @@ func init() {
func registerBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
- ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory)
}
var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
-func (a *CombinedApis) apiFingerprintStubDeps() []string {
+func (a *CombinedApis) bootclasspath(ctx android.ConfigAndErrorContext) []string {
+ return a.properties.Bootclasspath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil)
+}
+
+func (a *CombinedApis) systemServerClasspath(ctx android.ConfigAndErrorContext) []string {
+ return a.properties.System_server_classpath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil)
+}
+
+func (a *CombinedApis) apiFingerprintStubDeps(ctx android.BottomUpMutatorContext) []string {
ret := []string{}
ret = append(
ret,
- transformArray(a.properties.Bootclasspath, "", ".stubs")...,
+ transformArray(a.bootclasspath(ctx), "", ".stubs")...,
)
ret = append(
ret,
- transformArray(a.properties.Bootclasspath, "", ".stubs.system")...,
+ transformArray(a.bootclasspath(ctx), "", ".stubs.system")...,
)
ret = append(
ret,
- transformArray(a.properties.Bootclasspath, "", ".stubs.module_lib")...,
+ transformArray(a.bootclasspath(ctx), "", ".stubs.module_lib")...,
)
ret = append(
ret,
- transformArray(a.properties.System_server_classpath, "", ".stubs.system_server")...,
+ transformArray(a.systemServerClasspath(ctx), "", ".stubs.system_server")...,
)
return ret
}
func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) {
- ctx.AddDependency(ctx.Module(), nil, a.apiFingerprintStubDeps()...)
+ ctx.AddDependency(ctx.Module(), nil, a.apiFingerprintStubDeps(ctx)...)
}
func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -532,8 +538,8 @@ func createFullExportableApiLibraries(ctx android.LoadHookContext) {
}
func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
- bootclasspath := a.properties.Bootclasspath
- system_server_classpath := a.properties.System_server_classpath
+ bootclasspath := a.bootclasspath(ctx)
+ system_server_classpath := a.systemServerClasspath(ctx)
if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
sort.Strings(bootclasspath)
@@ -568,7 +574,6 @@ func combinedApisModuleFactory() android.Module {
module := &CombinedApis{}
module.AddProperties(&module.properties)
android.InitAndroidModule(module)
- android.InitDefaultableModule(module)
android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
return module
}
@@ -605,16 +610,3 @@ func remove(s []string, v string) []string {
}
return s2
}
-
-// Defaults
-type CombinedApisModuleDefaults struct {
- android.ModuleBase
- android.DefaultsModuleBase
-}
-
-func CombinedApisModuleDefaultsFactory() android.Module {
- module := &CombinedApisModuleDefaults{}
- module.AddProperties(&CombinedApisProperties{})
- android.InitDefaultsModule(module)
- return module
-}
diff --git a/api/api_test.go b/api/api_test.go
new file mode 100644
index 000000000000..47d167093b39
--- /dev/null
+++ b/api/api_test.go
@@ -0,0 +1,254 @@
+// Copyright (C) 2024 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 api
+
+import (
+ "android/soong/android"
+ "android/soong/java"
+ "fmt"
+ "testing"
+
+ "github.com/google/blueprint/proptools"
+)
+
+var prepareForTestWithCombinedApis = android.GroupFixturePreparers(
+ android.FixtureRegisterWithContext(registerBuildComponents),
+ java.PrepareForTestWithJavaBuildComponents,
+ android.FixtureAddTextFile("a/Android.bp", gatherRequiredDepsForTest()),
+ java.PrepareForTestWithJavaSdkLibraryFiles,
+ android.FixtureMergeMockFs(android.MockFS{
+ "a/api/current.txt": nil,
+ "a/api/removed.txt": nil,
+ "a/api/system-current.txt": nil,
+ "a/api/system-removed.txt": nil,
+ "a/api/test-current.txt": nil,
+ "a/api/test-removed.txt": nil,
+ "a/api/module-lib-current.txt": nil,
+ "a/api/module-lib-removed.txt": nil,
+ }),
+ android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+ variables.Allow_missing_dependencies = proptools.BoolPtr(true)
+ }),
+)
+
+func gatherRequiredDepsForTest() string {
+ var bp string
+
+ extraLibraryModules := []string{
+ "stable.core.platform.api.stubs",
+ "core-lambda-stubs",
+ "core.current.stubs",
+ "ext",
+ "framework",
+ "android_stubs_current.from-text",
+ "android_system_stubs_current.from-text",
+ "android_test_stubs_current.from-text",
+ "android_test_frameworks_core_stubs_current.from-text",
+ "android_module_lib_stubs_current.from-text",
+ "android_system_server_stubs_current.from-text",
+ "android_stubs_current.from-source",
+ "android_system_stubs_current.from-source",
+ "android_test_stubs_current.from-source",
+ "android_test_frameworks_core_stubs_current.from-source",
+ "android_module_lib_stubs_current.from-source",
+ "android_system_server_stubs_current.from-source",
+ "android_stubs_current_exportable.from-source",
+ "android_system_stubs_current_exportable.from-source",
+ "android_test_stubs_current_exportable.from-source",
+ "android_module_lib_stubs_current_exportable.from-source",
+ "android_system_server_stubs_current_exportable.from-source",
+ "stub-annotations",
+ }
+
+ extraSdkLibraryModules := []string{
+ "framework-virtualization",
+ "framework-location",
+ }
+
+ extraSystemModules := []string{
+ "core-public-stubs-system-modules",
+ "core-module-lib-stubs-system-modules",
+ "stable-core-platform-api-stubs-system-modules",
+ }
+
+ extraFilegroupModules := []string{
+ "non-updatable-current.txt",
+ "non-updatable-removed.txt",
+ "non-updatable-system-current.txt",
+ "non-updatable-system-removed.txt",
+ "non-updatable-test-current.txt",
+ "non-updatable-test-removed.txt",
+ "non-updatable-module-lib-current.txt",
+ "non-updatable-module-lib-removed.txt",
+ "non-updatable-system-server-current.txt",
+ "non-updatable-system-server-removed.txt",
+ "non-updatable-exportable-current.txt",
+ "non-updatable-exportable-removed.txt",
+ "non-updatable-exportable-system-current.txt",
+ "non-updatable-exportable-system-removed.txt",
+ "non-updatable-exportable-test-current.txt",
+ "non-updatable-exportable-test-removed.txt",
+ "non-updatable-exportable-module-lib-current.txt",
+ "non-updatable-exportable-module-lib-removed.txt",
+ "non-updatable-exportable-system-server-current.txt",
+ "non-updatable-exportable-system-server-removed.txt",
+ }
+
+ for _, extra := range extraLibraryModules {
+ bp += fmt.Sprintf(`
+ java_library {
+ name: "%s",
+ srcs: ["a.java"],
+ sdk_version: "none",
+ system_modules: "stable-core-platform-api-stubs-system-modules",
+ compile_dex: true,
+ }
+ `, extra)
+ }
+
+ for _, extra := range extraSdkLibraryModules {
+ bp += fmt.Sprintf(`
+ java_sdk_library {
+ name: "%s",
+ srcs: ["a.java"],
+ public: {
+ enabled: true,
+ },
+ system: {
+ enabled: true,
+ },
+ test: {
+ enabled: true,
+ },
+ module_lib: {
+ enabled: true,
+ },
+ api_packages: [
+ "foo",
+ ],
+ sdk_version: "core_current",
+ compile_dex: true,
+ annotations_enabled: true,
+ }
+ `, extra)
+ }
+
+ for _, extra := range extraFilegroupModules {
+ bp += fmt.Sprintf(`
+ filegroup {
+ name: "%[1]s",
+ }
+ `, extra)
+ }
+
+ for _, extra := range extraSystemModules {
+ bp += fmt.Sprintf(`
+ java_system_modules {
+ name: "%[1]s",
+ libs: ["%[1]s-lib"],
+ }
+ java_library {
+ name: "%[1]s-lib",
+ sdk_version: "none",
+ system_modules: "none",
+ }
+ `, extra)
+ }
+
+ bp += fmt.Sprintf(`
+ java_defaults {
+ name: "android.jar_defaults",
+ }
+ `)
+
+ return bp
+}
+
+func TestCombinedApisDefaults(t *testing.T) {
+
+ result := android.GroupFixturePreparers(
+ prepareForTestWithCombinedApis,
+ java.FixtureWithLastReleaseApis(
+ "framework-location", "framework-virtualization", "framework-foo", "framework-bar"),
+ android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+ variables.VendorVars = map[string]map[string]string{
+ "boolean_var": {
+ "for_testing": "true",
+ },
+ }
+ }),
+ ).RunTestWithBp(t, `
+ java_sdk_library {
+ name: "framework-foo",
+ srcs: ["a.java"],
+ public: {
+ enabled: true,
+ },
+ system: {
+ enabled: true,
+ },
+ test: {
+ enabled: true,
+ },
+ module_lib: {
+ enabled: true,
+ },
+ api_packages: [
+ "foo",
+ ],
+ sdk_version: "core_current",
+ annotations_enabled: true,
+ }
+
+ java_sdk_library {
+ name: "framework-bar",
+ srcs: ["a.java"],
+ public: {
+ enabled: true,
+ },
+ system: {
+ enabled: true,
+ },
+ test: {
+ enabled: true,
+ },
+ module_lib: {
+ enabled: true,
+ },
+ api_packages: [
+ "foo",
+ ],
+ sdk_version: "core_current",
+ annotations_enabled: true,
+ }
+
+ combined_apis {
+ name: "foo",
+ bootclasspath: [
+ "framework-bar",
+ ] + select(boolean_var_for_testing(), {
+ true: [
+ "framework-foo",
+ ],
+ default: [],
+ }),
+ }
+ `)
+
+ subModuleDependsOnSelectAppendedModule := java.CheckModuleHasDependency(t,
+ result.TestContext, "foo-current.txt", "", "framework-foo")
+ android.AssertBoolEquals(t, "Submodule expected to depend on the select-appended module",
+ true, subModuleDependsOnSelectAppendedModule)
+}
diff --git a/api/go.work b/api/go.work
index edd002e7efba..c09bee578b61 100644
--- a/api/go.work
+++ b/api/go.work
@@ -1,17 +1,17 @@
-go 1.18
+go 1.22
use (
.
- ../../../build/soong
../../../build/blueprint
+ ../../../build/soong
../../../external/go-cmp
../../../external/golang-protobuf
)
replace (
android/soong v0.0.0 => ../../../build/soong
- google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
github.com/google/blueprint v0.0.0 => ../../../build/blueprint
github.com/google/go-cmp v0.0.0 => ../../../external/go-cmp
go.starlark.net v0.0.0 => ../../../external/starlark-go
+ google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
)
diff --git a/core/api/current.txt b/core/api/current.txt
index 0f237212c768..69c409bb5261 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26569,7 +26569,7 @@ package android.media.midi {
package android.media.projection {
public final class MediaProjection {
- method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler);
method public void stop();
method public void unregisterCallback(@NonNull android.media.projection.MediaProjection.Callback);
@@ -54862,6 +54862,8 @@ package android.view.accessibility {
method @Deprecated public void addAction(int);
method public void addChild(android.view.View);
method public void addChild(android.view.View, int);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
method public boolean canOpenPopup();
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54890,6 +54892,7 @@ package android.view.accessibility {
method public int getInputType();
method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
method public int getMaxTextLength();
method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -54950,6 +54953,8 @@ package android.view.accessibility {
method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
method public boolean removeChild(android.view.View);
method public boolean removeChild(android.view.View, int);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
method public void setAccessibilityDataSensitive(boolean);
method public void setAccessibilityFocused(boolean);
method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 44c4ab4e1b57..88b5275d37f8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -423,6 +423,7 @@ package android.app {
public final class PictureInPictureParams implements android.os.Parcelable {
method public float getAspectRatioFloat();
method public float getExpandedAspectRatioFloat();
+ method public static boolean isSameAspectRatio(@NonNull android.graphics.Rect, @NonNull android.util.Rational);
}
public final class PictureInPictureUiState implements android.os.Parcelable {
@@ -618,6 +619,7 @@ package android.app.admin {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
+ method @FlaggedApi("android.app.admin.flags.provisioning_context_parameter") @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int, @Nullable String);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, int);
method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 845a346be593..ac3711366ec7 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1381,6 +1381,18 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
int toId = findLatestEventIdForTime(playTime);
handleAnimationEvents(-1, toId, playTime);
+
+ if (mSeekState.isActive()) {
+ // Pump a frame to the on-going animators
+ for (int i = 0; i < mPlayingSet.size(); i++) {
+ Node node = mPlayingSet.get(i);
+ if (!node.mEnded) {
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ }
+ }
+ }
+
+ // Remove all the finished anims
for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
if (mPlayingSet.get(i).mEnded) {
mPlayingSet.remove(i);
diff --git a/core/java/android/animation/OWNERS b/core/java/android/animation/OWNERS
index f3b330a02116..5223c870824c 100644
--- a/core/java/android/animation/OWNERS
+++ b/core/java/android/animation/OWNERS
@@ -3,3 +3,4 @@
romainguy@google.com
tianliu@google.com
adamp@google.com
+mount@google.com
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 36b1eaba89df..6df971a9cea8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2105,8 +2105,7 @@ public final class ActivityThread extends ClientTransactionHandler
@Override
public void scheduleTaskFragmentTransaction(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragmentTransaction transaction) throws RemoteException {
- // TODO(b/260873529): ITaskFragmentOrganizer can be cleanup to be a IBinder token
- // after flag removal.
+ // TODO(b/352665082): ITaskFragmentOrganizer can be cleanup to be a IBinder token
organizer.onTransactionReady(transaction);
}
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index 96d874ee110b..afe915eece26 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -654,6 +654,33 @@ public final class PictureInPictureParams implements Parcelable {
&& !hasSetSubtitle() && mIsLaunchIntoPip == null;
}
+ /**
+ * Compare a given {@link Rect} against the aspect ratio, with rounding error tolerance.
+ * @param bounds The {@link Rect} represents the source rect hint, this check is not needed
+ * if app provides a null source rect hint.
+ * @param aspectRatio {@link Rational} representation of aspect ratio, this check is not needed
+ * if app provides a null aspect ratio.
+ * @return {@code true} if the given {@link Rect} matches the aspect ratio.
+ * @hide
+ */
+ @SuppressWarnings("UnflaggedApi")
+ @TestApi
+ public static boolean isSameAspectRatio(@NonNull Rect bounds, @NonNull Rational aspectRatio) {
+ // Validations
+ if (bounds.isEmpty() || aspectRatio.floatValue() <= 0) {
+ return false;
+ }
+ // Check against both the width and height.
+ final int exactWidth = (aspectRatio.getNumerator() * bounds.height())
+ / aspectRatio.getDenominator();
+ if (Math.abs(exactWidth - bounds.width()) <= 1) {
+ return true;
+ }
+ final int exactHeight = (aspectRatio.getDenominator() * bounds.width())
+ / aspectRatio.getNumerator();
+ return Math.abs(exactHeight - bounds.height()) <= 1;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9437c748ee80..e73f4718732f 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -468,6 +468,11 @@ public final class SystemServiceRegistry {
public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE);
IVpnManager service = IVpnManager.Stub.asInterface(b);
+ if (service == null
+ && ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && android.server.Flags.allowRemovingVpnService()) {
+ throw new ServiceNotFoundException(Context.VPN_MANAGEMENT_SERVICE);
+ }
return new VpnManager(ctx, service);
}});
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fb0ce0d2d077..a7070b910f17 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9202,6 +9202,14 @@ public class DevicePolicyManager {
/**
* @hide
*/
+ @UnsupportedAppUsage
+ public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing) {
+ setActiveAdmin(policyReceiver, refreshing, myUserId());
+ }
+
+ /**
+ * @hide
+ */
@TestApi
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(allOf = {
@@ -9210,21 +9218,45 @@ public class DevicePolicyManager {
})
public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing,
int userHandle) {
- if (mService != null) {
- try {
- mService.setActiveAdmin(policyReceiver, refreshing, userHandle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ setActiveAdminInternal(policyReceiver, refreshing, userHandle, null);
}
/**
* @hide
*/
- @UnsupportedAppUsage
- public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing) {
- setActiveAdmin(policyReceiver, refreshing, myUserId());
+ @TestApi
+ @RequiresPermission(allOf = {
+ MANAGE_DEVICE_ADMINS,
+ INTERACT_ACROSS_USERS_FULL
+ })
+ @FlaggedApi(Flags.FLAG_PROVISIONING_CONTEXT_PARAMETER)
+ public void setActiveAdmin(
+ @NonNull ComponentName policyReceiver,
+ boolean refreshing,
+ int userHandle,
+ @Nullable String provisioningContext
+ ) {
+ setActiveAdminInternal(policyReceiver, refreshing, userHandle, provisioningContext);
+ }
+
+ private void setActiveAdminInternal(
+ @NonNull ComponentName policyReceiver,
+ boolean refreshing,
+ int userHandle,
+ @Nullable String provisioningContext
+ ) {
+ if (mService != null) {
+ try {
+ mService.setActiveAdmin(
+ policyReceiver,
+ refreshing,
+ userHandle,
+ provisioningContext
+ );
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -9678,7 +9710,7 @@ public class DevicePolicyManager {
if (mService != null) {
try {
final int myUserId = myUserId();
- mService.setActiveAdmin(admin, false, myUserId);
+ mService.setActiveAdmin(admin, false, myUserId, null);
return mService.setProfileOwner(admin, myUserId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d1837132e1a4..381f9963789b 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -160,7 +160,8 @@ interface IDevicePolicyManager {
void setKeyguardDisabledFeatures(in ComponentName who, String callerPackageName, int which, boolean parent);
int getKeyguardDisabledFeatures(in ComponentName who, int userHandle, boolean parent);
- void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle);
+ void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing,
+ int userHandle, String provisioningContext);
boolean isAdminActive(in ComponentName policyReceiver, int userHandle);
List<ComponentName> getActiveAdmins(int userHandle);
@UnsupportedAppUsage
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 82271129c69e..c789af32e2b1 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -393,3 +393,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "provisioning_context_parameter"
+ namespace: "enterprise"
+ description: "Add provisioningContext to store metadata about when the admin was set"
+ bug: "326525847"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
index 598bd8a75637..e86ca37b99ca 100644
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ b/core/java/android/app/servertransaction/ObjectPool.java
@@ -16,70 +16,39 @@
package android.app.servertransaction;
-import com.android.window.flags.Flags;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* An object pool that can provide reused objects if available.
+ *
* @hide
+ * @deprecated This class is deprecated. Directly create new instances of objects instead of
+ * obtaining them from this pool.
+ * TODO(b/311089192): Clean up usages of the pool.
*/
+@Deprecated
class ObjectPool {
- private static final Object sPoolSync = new Object();
- private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap =
- new HashMap<>();
-
- private static final int MAX_POOL_SIZE = 50;
-
/**
* Obtain an instance of a specific class from the pool
- * @param itemClass The class of the object we're looking for.
+ *
+ * @param ignoredItemClass The class of the object we're looking for.
* @return An instance or null if there is none.
+ * @deprecated This method is deprecated. Directly create new instances of objects instead of
+ * obtaining them from this pool.
*/
- public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) {
- if (Flags.disableObjectPool()) {
- return null;
- }
- synchronized (sPoolSync) {
- @SuppressWarnings("unchecked")
- final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass);
- if (itemPool != null && !itemPool.isEmpty()) {
- return itemPool.remove(itemPool.size() - 1);
- }
- return null;
- }
+ @Deprecated
+ public static <T extends ObjectPoolItem> T obtain(Class<T> ignoredItemClass) {
+ return null;
}
/**
* Recycle the object to the pool. The object should be properly cleared before this.
- * @param item The object to recycle.
+ *
+ * @param ignoredItem The object to recycle.
* @see ObjectPoolItem#recycle()
+ * @deprecated This method is deprecated. The object pool is no longer used, so there's
+ * no need to recycle objects.
*/
- public static <T extends ObjectPoolItem> void recycle(T item) {
- if (Flags.disableObjectPool()) {
- return;
- }
- synchronized (sPoolSync) {
- @SuppressWarnings("unchecked")
- ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass());
- if (itemPool == null) {
- itemPool = new ArrayList<>();
- sPoolMap.put(item.getClass(), itemPool);
- }
- // Check if the item is already in the pool
- final int size = itemPool.size();
- for (int i = 0; i < size; i++) {
- if (itemPool.get(i) == item) {
- throw new IllegalStateException("Trying to recycle already recycled item");
- }
- }
-
- if (size < MAX_POOL_SIZE) {
- itemPool.add(item);
- }
- }
+ @Deprecated
+ public static <T extends ObjectPoolItem> void recycle(T ignoredItem) {
}
}
diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java
index 17bd4f30640f..0141f6eff53b 100644
--- a/core/java/android/app/servertransaction/ObjectPoolItem.java
+++ b/core/java/android/app/servertransaction/ObjectPoolItem.java
@@ -18,12 +18,20 @@ package android.app.servertransaction;
/**
* Base interface for all lifecycle items that can be put in object pool.
+ *
* @hide
+ * @deprecated This interface is deprecated. Objects should no longer be pooled.
+ * TODO(b/311089192): Clean up usages of this interface.
*/
+@Deprecated
public interface ObjectPoolItem {
/**
* Clear the contents of the item and putting it to a pool. The implementation should call
* {@link ObjectPool#recycle(ObjectPoolItem)} passing itself.
+ *
+ * @deprecated This method is deprecated. The object pool is no longer used, so there's
+ * no need to recycle objects.
*/
+ @Deprecated
void recycle();
}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c2c7b81871df..5a3970295ac2 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -327,3 +327,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "fix_large_display_private_space_settings"
+ namespace: "profile_experiences"
+ description: "Fix tablet and foldable specific bugs for private space"
+ bug: "342563741"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 0f944cfb7f67..51024ba64bd9 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -36,3 +36,10 @@ flag {
description: "Enable identifying midi device using USB sysfs"
bug: "333778731"
}
+
+flag {
+ name: "enable_udc_sysfs_usb_state_update"
+ namespace: "system_sw_usb"
+ description: "Enable usb state update based on udc sysfs"
+ bug: "339241080"
+}
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index 67e21957839a..c7f8878f104e 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -55,7 +55,7 @@ public final class AggregateBatteryConsumer extends BatteryConsumer {
@Override
public void dump(PrintWriter pw, boolean skipEmptyComponents) {
- mPowerComponents.dump(pw, skipEmptyComponents);
+ mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, skipEmptyComponents);
}
@Override
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 744f6a8d61ac..2447ff93fdbc 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -19,14 +19,19 @@ package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.database.Cursor;
import android.database.CursorWindow;
+import android.util.IntArray;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* Interface for objects containing battery attribution data.
@@ -192,31 +197,106 @@ public abstract class BatteryConsumer {
sProcessStateNames[PROCESS_STATE_CACHED] = "cached";
}
- private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = {
- POWER_COMPONENT_CPU,
- POWER_COMPONENT_MOBILE_RADIO,
- POWER_COMPONENT_WIFI,
- POWER_COMPONENT_BLUETOOTH,
- POWER_COMPONENT_AUDIO,
- POWER_COMPONENT_VIDEO,
- POWER_COMPONENT_FLASHLIGHT,
- POWER_COMPONENT_CAMERA,
- POWER_COMPONENT_GNSS,
+ private static final IntArray SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE;
+ static {
+ int[] supportedPowerComponents = {
+ POWER_COMPONENT_CPU,
+ POWER_COMPONENT_MOBILE_RADIO,
+ POWER_COMPONENT_WIFI,
+ POWER_COMPONENT_BLUETOOTH,
+ POWER_COMPONENT_AUDIO,
+ POWER_COMPONENT_VIDEO,
+ POWER_COMPONENT_FLASHLIGHT,
+ POWER_COMPONENT_CAMERA,
+ POWER_COMPONENT_GNSS};
+ Arrays.sort(supportedPowerComponents);
+ SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = IntArray.wrap(supportedPowerComponents);
};
static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
static final int COLUMN_COUNT = 1;
/**
+ * Identifiers of consumed power aggregations per SCREEN state.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"SCREEN_STATE_"}, value = {
+ SCREEN_STATE_UNSPECIFIED,
+ SCREEN_STATE_ANY,
+ SCREEN_STATE_ON,
+ SCREEN_STATE_OTHER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScreenState {
+ }
+
+ public static final int SCREEN_STATE_UNSPECIFIED = 0;
+ public static final int SCREEN_STATE_ANY = SCREEN_STATE_UNSPECIFIED;
+ public static final int SCREEN_STATE_ON = 1;
+ public static final int SCREEN_STATE_OTHER = 2; // Off, doze etc
+
+ public static final int SCREEN_STATE_COUNT = 3;
+
+ private static final String[] sScreenStateNames = new String[SCREEN_STATE_COUNT];
+
+ static {
+ // Assign individually to avoid future mismatch
+ sScreenStateNames[SCREEN_STATE_UNSPECIFIED] = "unspecified";
+ sScreenStateNames[SCREEN_STATE_ON] = "on";
+ sScreenStateNames[SCREEN_STATE_OTHER] = "off/doze";
+ }
+
+ /**
+ * Identifiers of consumed power aggregations per POWER state.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"POWER_STATE_"}, value = {
+ POWER_STATE_UNSPECIFIED,
+ POWER_STATE_ANY,
+ POWER_STATE_BATTERY,
+ POWER_STATE_OTHER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PowerState {
+ }
+
+ public static final int POWER_STATE_UNSPECIFIED = 0;
+ public static final int POWER_STATE_ANY = POWER_STATE_UNSPECIFIED;
+ public static final int POWER_STATE_BATTERY = 1;
+ public static final int POWER_STATE_OTHER = 2; // Plugged in, or on wireless charger, etc.
+
+ public static final int POWER_STATE_COUNT = 3;
+
+ private static final String[] sPowerStateNames = new String[POWER_STATE_COUNT];
+
+ static {
+ // Assign individually to avoid future mismatch
+ sPowerStateNames[POWER_STATE_UNSPECIFIED] = "unspecified";
+ sPowerStateNames[POWER_STATE_BATTERY] = "on battery";
+ sPowerStateNames[POWER_STATE_OTHER] = "not on battery";
+ }
+
+ /**
* Identifies power attribution dimensions that a caller is interested in.
*/
public static final class Dimensions {
public final @PowerComponent int powerComponent;
public final @ProcessState int processState;
+ public final @ScreenState int screenState;
+ public final @PowerState int powerState;
- public Dimensions(int powerComponent, int processState) {
+ public Dimensions(@PowerComponent int powerComponent, @ProcessState int processState) {
+ this(powerComponent, processState, SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+ }
+
+ public Dimensions(@PowerComponent int powerComponent, int processState,
+ @ScreenState int screenState, @PowerState int powerState) {
this.powerComponent = powerComponent;
this.processState = processState;
+ this.screenState = screenState;
+ this.powerState = powerState;
}
@Override
@@ -234,6 +314,20 @@ public abstract class BatteryConsumer {
sb.append("processState=").append(sProcessStateNames[processState]);
dimensionSpecified = true;
}
+ if (screenState != SCREEN_STATE_ANY) {
+ if (dimensionSpecified) {
+ sb.append(", ");
+ }
+ sb.append("screenState=").append(screenStateToString(screenState));
+ dimensionSpecified = true;
+ }
+ if (powerState != POWER_STATE_ANY) {
+ if (dimensionSpecified) {
+ sb.append(", ");
+ }
+ sb.append("powerState=").append(powerStateToString(powerState));
+ dimensionSpecified = true;
+ }
if (!dimensionSpecified) {
sb.append("any components and process states");
}
@@ -242,7 +336,8 @@ public abstract class BatteryConsumer {
}
public static final Dimensions UNSPECIFIED_DIMENSIONS =
- new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY);
+ new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY, SCREEN_STATE_ANY,
+ POWER_STATE_ANY);
/**
* Identifies power attribution dimensions that are captured by a data element of
@@ -258,52 +353,93 @@ public abstract class BatteryConsumer {
public static final class Key {
public final @PowerComponent int powerComponent;
public final @ProcessState int processState;
+ public final @ScreenState int screenState;
+ public final @PowerState int powerState;
final int mPowerModelColumnIndex;
final int mPowerColumnIndex;
final int mDurationColumnIndex;
- private String mShortString;
- private Key(int powerComponent, int processState, int powerModelColumnIndex,
+ private Key(@PowerComponent int powerComponent, @ProcessState int processState,
+ @ScreenState int screenState, @PowerState int powerState, int powerModelColumnIndex,
int powerColumnIndex, int durationColumnIndex) {
this.powerComponent = powerComponent;
this.processState = processState;
+ this.screenState = screenState;
+ this.powerState = powerState;
mPowerModelColumnIndex = powerModelColumnIndex;
mPowerColumnIndex = powerColumnIndex;
mDurationColumnIndex = durationColumnIndex;
}
+ /**
+ * Returns true if this key should be included in an enumeration parameterized with
+ * the supplied dimensions.
+ */
+ boolean matches(@PowerComponent int powerComponent, @ProcessState int processState,
+ @ScreenState int screenState, @PowerState int powerState) {
+ if (powerComponent != POWER_COMPONENT_ANY && this.powerComponent != powerComponent) {
+ return false;
+ }
+ if (processState != PROCESS_STATE_ANY && this.processState != processState) {
+ return false;
+ }
+ if (screenState != SCREEN_STATE_ANY && this.screenState != screenState) {
+ return false;
+ }
+ if (powerState != POWER_STATE_ANY && this.powerState != powerState) {
+ return false;
+ }
+ return true;
+ }
+
@SuppressWarnings("EqualsUnsafeCast")
@Override
public boolean equals(Object o) {
// Skipping null and class check for performance
final Key key = (Key) o;
return powerComponent == key.powerComponent
- && processState == key.processState;
+ && processState == key.processState
+ && screenState == key.screenState
+ && powerState == key.powerState;
}
@Override
public int hashCode() {
int result = powerComponent;
result = 31 * result + processState;
+ result = 31 * result + screenState;
+ result = 31 * result + powerState;
return result;
}
/**
* Returns a string suitable for use in dumpsys.
*/
- public String toShortString() {
- if (mShortString == null) {
- StringBuilder sb = new StringBuilder();
- sb.append(powerComponentIdToString(powerComponent));
- if (processState != PROCESS_STATE_UNSPECIFIED) {
- sb.append(':');
- sb.append(processStateToString(processState));
- }
- mShortString = sb.toString();
+ public static String toString(@PowerComponent int powerComponent,
+ @ProcessState int processState, @ScreenState int screenState,
+ @PowerState int powerState) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(powerComponentIdToString(powerComponent));
+ if (processState != PROCESS_STATE_UNSPECIFIED) {
+ sb.append(':');
+ sb.append(processStateToString(processState));
+ }
+ if (screenState != SCREEN_STATE_UNSPECIFIED) {
+ sb.append(":scr-");
+ sb.append(sScreenStateNames[screenState]);
+ }
+ if (powerState != POWER_STATE_UNSPECIFIED) {
+ sb.append(":pwr-");
+ sb.append(sPowerStateNames[powerState]);
}
- return mShortString;
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString(powerComponent, processState, screenState, powerState);
}
}
@@ -335,11 +471,18 @@ public abstract class BatteryConsumer {
}
/**
+ * Returns the amount of usage time aggregated over the specified dimensions, in millis.
+ */
+ public long getUsageDurationMillis(@NonNull Dimensions dimensions) {
+ return mPowerComponents.getUsageDurationMillis(dimensions);
+ }
+
+ /**
* Returns keys for various power values attributed to the specified component
* held by this BatteryUsageStats object.
*/
public Key[] getKeys(@PowerComponent int componentId) {
- return mData.getKeys(componentId);
+ return mData.layout.getKeys(componentId);
}
/**
@@ -347,14 +490,16 @@ public abstract class BatteryConsumer {
* for all values of other dimensions such as process state.
*/
public Key getKey(@PowerComponent int componentId) {
- return mData.getKey(componentId, PROCESS_STATE_UNSPECIFIED);
+ return mData.layout.getKey(componentId, PROCESS_STATE_UNSPECIFIED, SCREEN_STATE_UNSPECIFIED,
+ POWER_STATE_UNSPECIFIED);
}
/**
* Returns the key for the power attributed to the specified component and process state.
*/
public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
- return mData.getKey(componentId, processState);
+ return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED,
+ POWER_STATE_UNSPECIFIED);
}
/**
@@ -365,8 +510,8 @@ public abstract class BatteryConsumer {
* @return Amount of consumed power in mAh.
*/
public double getConsumedPower(@PowerComponent int componentId) {
- return mPowerComponents.getConsumedPower(
- mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
+ return mPowerComponents.getConsumedPower(componentId, PROCESS_STATE_UNSPECIFIED,
+ SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
}
/**
@@ -388,7 +533,8 @@ public abstract class BatteryConsumer {
*/
public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
return mPowerComponents.getPowerModel(
- mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
+ mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED,
+ SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED));
}
/**
@@ -507,6 +653,20 @@ public abstract class BatteryConsumer {
}
/**
+ * Returns the human-readable name of the specified power state (on battery or not)
+ */
+ public static String powerStateToString(@PowerState int powerState) {
+ return sPowerStateNames[powerState];
+ }
+
+ /**
+ * Returns the human-readable name of the specified screen state (on or off/doze)
+ */
+ public static String screenStateToString(@ScreenState int screenState) {
+ return sScreenStateNames[screenState];
+ }
+
+ /**
* Prints the stats in a human-readable format.
*/
public void dump(PrintWriter pw) {
@@ -591,42 +751,11 @@ public abstract class BatteryConsumer {
return new BatteryConsumerData(cursorWindow, cursorRow, layout);
}
- public Key[] getKeys(int componentId) {
- return layout.keys[componentId];
- }
-
- Key getKeyOrThrow(int componentId, int processState) {
- Key key = getKey(componentId, processState);
- if (key == null) {
- if (processState == PROCESS_STATE_ANY) {
- throw new IllegalArgumentException(
- "Unsupported power component ID: " + componentId);
- } else {
- throw new IllegalArgumentException(
- "Unsupported power component ID: " + componentId
- + " process state: " + processState);
- }
- }
- return key;
- }
-
- Key getKey(int componentId, int processState) {
- if (componentId >= POWER_COMPONENT_COUNT) {
- return null;
- }
-
- if (processState == PROCESS_STATE_ANY) {
- // The 0-th key for each component corresponds to the roll-up,
- // across all dimensions. We might as well skip the iteration over the array.
- return layout.keys[componentId][0];
- } else {
- for (Key key : layout.keys[componentId]) {
- if (key.processState == processState) {
- return key;
- }
- }
+ boolean hasValue(int columnIndex) {
+ if (mCursorRow == -1) {
+ return false;
}
- return null;
+ return mCursorWindow.getType(mCursorRow, columnIndex) != Cursor.FIELD_TYPE_NULL;
}
void putInt(int columnIndex, int value) {
@@ -693,113 +822,158 @@ public abstract class BatteryConsumer {
public final int customPowerComponentCount;
public final boolean powerModelsIncluded;
public final boolean processStateDataIncluded;
- public final Key[][] keys;
+ public final boolean screenStateDataIncluded;
+ public final boolean powerStateDataIncluded;
+ public final Key[] keys;
+ public final SparseArray<Key> indexedKeys;
public final int totalConsumedPowerColumnIndex;
public final int firstCustomConsumedPowerColumn;
public final int firstCustomUsageDurationColumn;
public final int columnCount;
- public final Key[][] processStateKeys;
+ private Key[][] mPerComponentKeys;
private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
- boolean powerModelsIncluded, boolean includeProcessStateData) {
+ boolean powerModelsIncluded, boolean includeProcessStateData,
+ boolean includeScreenState, boolean includePowerState) {
this.customPowerComponentNames = customPowerComponentNames;
this.customPowerComponentCount = customPowerComponentNames.length;
this.powerModelsIncluded = powerModelsIncluded;
this.processStateDataIncluded = includeProcessStateData;
+ this.screenStateDataIncluded = includeScreenState;
+ this.powerStateDataIncluded = includePowerState;
int columnIndex = firstColumn;
totalConsumedPowerColumnIndex = columnIndex++;
- keys = new Key[POWER_COMPONENT_COUNT][];
+ ArrayList<Key> keyList = new ArrayList<>();
+ for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) {
+ if (!includeScreenState && screenState != SCREEN_STATE_UNSPECIFIED) {
+ continue;
+ }
+ for (int powerState = 0; powerState < POWER_STATE_COUNT; powerState++) {
+ if (!includePowerState && powerState != POWER_STATE_UNSPECIFIED) {
+ continue;
+ }
+ for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+ columnIndex = addKeys(keyList, powerModelsIncluded, includeProcessStateData,
+ componentId, screenState, powerState, columnIndex);
+ }
+ }
+ }
+
+ firstCustomConsumedPowerColumn = columnIndex;
+ columnIndex += customPowerComponentCount;
+
+ firstCustomUsageDurationColumn = columnIndex;
+ columnIndex += customPowerComponentCount;
+
+ columnCount = columnIndex;
- ArrayList<Key> perComponentKeys = new ArrayList<>();
- for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
- perComponentKeys.clear();
+ keys = keyList.toArray(KEY_ARRAY);
+ indexedKeys = new SparseArray<>(keys.length);
+ for (int i = 0; i < keys.length; i++) {
+ Key key = keys[i];
+ int index = keyIndex(key.powerComponent, key.processState, key.screenState,
+ key.powerState);
+ indexedKeys.put(index, key);
+ }
+ }
- // Declare the Key for the power component, ignoring other dimensions.
- perComponentKeys.add(
- new Key(componentId, PROCESS_STATE_ANY,
+ private int addKeys(List<Key> keys, boolean powerModelsIncluded,
+ boolean includeProcessStateData, int componentId,
+ int screenState, int powerState, int columnIndex) {
+ keys.add(new Key(componentId, PROCESS_STATE_ANY, screenState, powerState,
+ powerModelsIncluded
+ ? columnIndex++
+ : POWER_MODEL_NOT_INCLUDED, // power model
+ columnIndex++, // power
+ columnIndex++ // usage duration
+ ));
+
+ // Declare Keys for all process states, if needed
+ if (includeProcessStateData) {
+ boolean isSupported = SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE
+ .binarySearch(componentId) >= 0;
+ if (isSupported) {
+ for (int processState = 0; processState < PROCESS_STATE_COUNT;
+ processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ keys.add(new Key(componentId, processState, screenState, powerState,
powerModelsIncluded
? columnIndex++
- : POWER_MODEL_NOT_INCLUDED, // power model
+ : POWER_MODEL_NOT_INCLUDED, // power model
columnIndex++, // power
columnIndex++ // usage duration
));
-
- // Declare Keys for all process states, if needed
- if (includeProcessStateData) {
- boolean isSupported = false;
- for (int id : SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE) {
- if (id == componentId) {
- isSupported = true;
- break;
- }
- }
- if (isSupported) {
- for (int processState = 0; processState < PROCESS_STATE_COUNT;
- processState++) {
- if (processState == PROCESS_STATE_UNSPECIFIED) {
- continue;
- }
-
- perComponentKeys.add(
- new Key(componentId, processState,
- powerModelsIncluded
- ? columnIndex++
- : POWER_MODEL_NOT_INCLUDED, // power model
- columnIndex++, // power
- columnIndex++ // usage duration
- ));
- }
}
}
-
- keys[componentId] = perComponentKeys.toArray(KEY_ARRAY);
}
+ return columnIndex;
+ }
- if (includeProcessStateData) {
- processStateKeys = new Key[BatteryConsumer.PROCESS_STATE_COUNT][];
- ArrayList<Key> perProcStateKeys = new ArrayList<>();
- for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) {
- if (processState == PROCESS_STATE_UNSPECIFIED) {
- continue;
- }
+ Key getKey(@PowerComponent int componentId, @ProcessState int processState,
+ @ScreenState int screenState, @PowerState int powerState) {
+ return indexedKeys.get(keyIndex(componentId, processState, screenState, powerState));
+ }
- perProcStateKeys.clear();
- for (int i = 0; i < keys.length; i++) {
- for (int j = 0; j < keys[i].length; j++) {
- if (keys[i][j].processState == processState) {
- perProcStateKeys.add(keys[i][j]);
- }
+ Key getKeyOrThrow(@PowerComponent int componentId, @ProcessState int processState,
+ @ScreenState int screenState, @PowerState int powerState) {
+ Key key = getKey(componentId, processState, screenState, powerState);
+ if (key == null) {
+ throw new IllegalArgumentException(
+ "Unsupported power component ID: " + Key.toString(componentId, processState,
+ screenState, powerState));
+ }
+ return key;
+ }
+
+ public Key[] getKeys(@PowerComponent int componentId) {
+ synchronized (this) {
+ if (mPerComponentKeys == null) {
+ mPerComponentKeys = new Key[BatteryConsumer.POWER_COMPONENT_COUNT][];
+ }
+ Key[] componentKeys = mPerComponentKeys[componentId];
+ if (componentKeys == null) {
+ ArrayList<Key> out = new ArrayList<>();
+ for (Key key : keys) {
+ if (key.powerComponent == componentId) {
+ out.add(key);
}
}
- processStateKeys[processState] = perProcStateKeys.toArray(KEY_ARRAY);
+ componentKeys = out.toArray(new Key[out.size()]);
+ mPerComponentKeys[componentId] = componentKeys;
}
- } else {
- processStateKeys = null;
+ return componentKeys;
}
+ }
- firstCustomConsumedPowerColumn = columnIndex;
- columnIndex += customPowerComponentCount;
-
- firstCustomUsageDurationColumn = columnIndex;
- columnIndex += customPowerComponentCount;
-
- columnCount = columnIndex;
+ private int keyIndex(@PowerComponent int componentId, @ProcessState int processState,
+ @ScreenState int screenState, @PowerState int powerState) {
+ // [CCCCCCPPPSSBB]
+ // C - component ID
+ // P - process state
+ // S - screen state
+ // B - power state
+ return componentId << 7 | processState << 4 | screenState << 2 | powerState;
}
}
static BatteryConsumerDataLayout createBatteryConsumerDataLayout(
String[] customPowerComponentNames, boolean includePowerModels,
- boolean includeProcessStateData) {
+ boolean includeProcessStateData, boolean includeScreenStateData,
+ boolean includePowerStateData) {
int columnCount = BatteryConsumer.COLUMN_COUNT;
columnCount = Math.max(columnCount, AggregateBatteryConsumer.COLUMN_COUNT);
columnCount = Math.max(columnCount, UidBatteryConsumer.COLUMN_COUNT);
columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT);
return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames,
- includePowerModels, includeProcessStateData);
+ includePowerModels, includeProcessStateData, includeScreenStateData,
+ includePowerStateData);
}
protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
@@ -816,12 +990,19 @@ public abstract class BatteryConsumer {
@Nullable
public Key[] getKeys(@PowerComponent int componentId) {
- return mData.getKeys(componentId);
+ return mData.layout.getKeys(componentId);
}
@Nullable
public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
- return mData.getKey(componentId, processState);
+ return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED,
+ POWER_STATE_UNSPECIFIED);
+ }
+
+ @Nullable
+ public Key getKey(@PowerComponent int componentId, @ProcessState int processState,
+ @ScreenState int screenState, @PowerState int powerState) {
+ return mData.layout.getKey(componentId, processState, screenState, powerState);
}
/**
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 61cc23d994f3..dd484f6bace5 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -102,9 +102,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
static final String XML_ATTR_SCOPE = "scope";
static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_";
static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data";
+ static final String XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA = "includes_screen_state_data";
+ static final String XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA = "includes_power_state_data";
static final String XML_ATTR_START_TIMESTAMP = "start_timestamp";
static final String XML_ATTR_END_TIMESTAMP = "end_timestamp";
static final String XML_ATTR_PROCESS_STATE = "process_state";
+ static final String XML_ATTR_SCREEN_STATE = "screen_state";
+ static final String XML_ATTR_POWER_STATE = "power_state";
static final String XML_ATTR_POWER = "power";
static final String XML_ATTR_DURATION = "duration";
static final String XML_ATTR_MODEL = "model";
@@ -144,10 +148,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private final String[] mCustomPowerComponentNames;
private final boolean mIncludesPowerModels;
private final boolean mIncludesProcessStateData;
+ private final boolean mIncludesScreenStateData;
+ private final boolean mIncludesPowerStateData;
private final List<UidBatteryConsumer> mUidBatteryConsumers;
private final List<UserBatteryConsumer> mUserBatteryConsumers;
private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
private final BatteryStatsHistory mBatteryStatsHistory;
+ private BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private CursorWindow mBatteryConsumersCursorWindow;
private BatteryUsageStats(@NonNull Builder builder) {
@@ -165,6 +172,9 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
mIncludesPowerModels = builder.mIncludePowerModels;
mIncludesProcessStateData = builder.mIncludesProcessStateData;
+ mIncludesScreenStateData = builder.mIncludesScreenStateData;
+ mIncludesPowerStateData = builder.mIncludesPowerStateData;
+ mBatteryConsumerDataLayout = builder.mBatteryConsumerDataLayout;
mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow;
double totalPowerMah = 0;
@@ -347,11 +357,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
mCustomPowerComponentNames = source.readStringArray();
mIncludesPowerModels = source.readBoolean();
mIncludesProcessStateData = source.readBoolean();
+ mIncludesScreenStateData = source.readBoolean();
+ mIncludesPowerStateData = source.readBoolean();
mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
- BatteryConsumer.BatteryConsumerDataLayout dataLayout =
- BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames,
- mIncludesPowerModels, mIncludesProcessStateData);
+ mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
+ mCustomPowerComponentNames, mIncludesPowerModels, mIncludesProcessStateData,
+ mIncludesScreenStateData, mIncludesPowerStateData);
final int numRows = mBatteryConsumersCursorWindow.getNumRows();
@@ -363,7 +375,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
for (int i = 0; i < numRows; i++) {
final BatteryConsumer.BatteryConsumerData data =
new BatteryConsumer.BatteryConsumerData(mBatteryConsumersCursorWindow, i,
- dataLayout);
+ mBatteryConsumerDataLayout);
int consumerType = mBatteryConsumersCursorWindow.getInt(i,
BatteryConsumer.COLUMN_INDEX_BATTERY_CONSUMER_TYPE);
@@ -405,6 +417,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
dest.writeStringArray(mCustomPowerComponentNames);
dest.writeBoolean(mIncludesPowerModels);
dest.writeBoolean(mIncludesProcessStateData);
+ dest.writeBoolean(mIncludesScreenStateData);
+ dest.writeBoolean(mIncludesPowerStateData);
mBatteryConsumersCursorWindow.writeToParcel(dest, flags);
@@ -598,23 +612,16 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) {
- final double devicePowerMah = deviceConsumer.getConsumedPower(key);
- final double appsPowerMah = appsConsumer.getConsumedPower(key);
- if (devicePowerMah == 0 && appsPowerMah == 0) {
- continue;
- }
-
- String label = BatteryConsumer.powerComponentIdToString(componentId);
- if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- label = label
- + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
- }
- printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah,
- mIncludesPowerModels ? deviceConsumer.getPowerModel(key)
- : BatteryConsumer.POWER_MODEL_UNDEFINED,
- deviceConsumer.getUsageDurationMillis(key));
+ final double devicePowerMah = deviceConsumer.getConsumedPower(componentId);
+ final double appsPowerMah = appsConsumer.getConsumedPower(componentId);
+ if (devicePowerMah == 0 && appsPowerMah == 0) {
+ continue;
}
+
+ printPowerComponent(pw, prefix, BatteryConsumer.powerComponentIdToString(componentId),
+ devicePowerMah, appsPowerMah,
+ BatteryConsumer.POWER_MODEL_UNDEFINED,
+ deviceConsumer.getUsageDurationMillis(componentId));
}
for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
@@ -635,6 +642,59 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
deviceConsumer.getUsageDurationForCustomComponentMillis(componentId));
}
+ if (mIncludesScreenStateData || mIncludesPowerStateData) {
+ String prefixPlus = prefix + " ";
+ StringBuilder stateLabel = new StringBuilder();
+ int screenState = BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
+ int powerState = BatteryConsumer.POWER_STATE_UNSPECIFIED;
+ for (BatteryConsumer.Key key : mBatteryConsumerDataLayout.keys) {
+ if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ if (key.screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED
+ && key.powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ // Totals already printed earlier in this method
+ continue;
+ }
+
+ final double devicePowerMah = deviceConsumer.getConsumedPower(key);
+ final double appsPowerMah = appsConsumer.getConsumedPower(key);
+ if (devicePowerMah == 0 && appsPowerMah == 0) {
+ continue;
+ }
+
+ if (key.screenState != screenState || key.powerState != powerState) {
+ screenState = key.screenState;
+ powerState = key.powerState;
+
+ boolean empty = true;
+ stateLabel.setLength(0);
+ stateLabel.append(" (");
+ if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ stateLabel.append(BatteryConsumer.powerStateToString(powerState));
+ empty = false;
+ }
+ if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ if (!empty) {
+ stateLabel.append(", ");
+ }
+ stateLabel.append("screen ").append(
+ BatteryConsumer.screenStateToString(screenState));
+ empty = false;
+ }
+ if (!empty) {
+ stateLabel.append(")");
+ pw.println(stateLabel);
+ }
+ }
+ String label = BatteryConsumer.powerComponentIdToString(key.powerComponent);
+ printPowerComponent(pw, prefixPlus, label, devicePowerMah, appsPowerMah,
+ mIncludesPowerModels ? deviceConsumer.getPowerModel(key)
+ : BatteryConsumer.POWER_MODEL_UNDEFINED,
+ deviceConsumer.getUsageDurationMillis(key));
+ }
+ }
dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers());
dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
pw.println();
@@ -643,7 +703,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private void printPowerComponent(PrintWriter pw, String prefix, String label,
double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
StringBuilder sb = new StringBuilder();
- sb.append(prefix).append(" ").append(label).append(": ")
+ sb.append(prefix).append(" ").append(label).append(": ")
.append(BatteryStats.formatCharge(devicePowerMah));
if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
&& powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
@@ -657,7 +717,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
BatteryStats.formatTimeMs(sb, durationMs);
}
- pw.println(sb.toString());
+ pw.println(sb);
}
private void dumpSortedBatteryConsumers(PrintWriter pw, String prefix,
@@ -670,9 +730,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
continue;
}
pw.print(prefix);
- pw.print(" ");
+ pw.print(" ");
consumer.dump(pw);
- pw.println();
}
}
@@ -686,6 +745,10 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
}
serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA,
mIncludesProcessStateData);
+ serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA,
+ mIncludesScreenStateData);
+ serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA,
+ mIncludesPowerStateData);
serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs);
serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs);
serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs);
@@ -732,9 +795,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
final boolean includesProcStateData = parser.getAttributeBoolean(null,
XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
+ final boolean includesScreenStateData = parser.getAttributeBoolean(null,
+ XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA, false);
+ final boolean includesPowerStateData = parser.getAttributeBoolean(null,
+ XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA, false);
builder = new Builder(customComponentNames.toArray(new String[0]), true,
- includesProcStateData, 0);
+ includesProcStateData, includesScreenStateData, includesPowerStateData, 0);
builder.setStatsStartTimestamp(
parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
@@ -818,6 +885,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private final String[] mCustomPowerComponentNames;
private final boolean mIncludePowerModels;
private final boolean mIncludesProcessStateData;
+ private final boolean mIncludesScreenStateData;
+ private final boolean mIncludesPowerStateData;
private final double mMinConsumedPowerThreshold;
private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private long mStatsStartTimestampMs;
@@ -839,21 +908,24 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private BatteryStatsHistory mBatteryStatsHistory;
public Builder(@NonNull String[] customPowerComponentNames) {
- this(customPowerComponentNames, false, false, 0);
+ this(customPowerComponentNames, false, false, false, false, 0);
}
public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
- boolean includeProcessStateData, double minConsumedPowerThreshold) {
+ boolean includeProcessStateData, boolean includeScreenStateData,
+ boolean includesPowerStateData, double minConsumedPowerThreshold) {
mBatteryConsumersCursorWindow =
new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
- mBatteryConsumerDataLayout =
- BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames,
- includePowerModels, includeProcessStateData);
+ mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
+ customPowerComponentNames, includePowerModels, includeProcessStateData,
+ includeScreenStateData, includesPowerStateData);
mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
mCustomPowerComponentNames = customPowerComponentNames;
mIncludePowerModels = includePowerModels;
mIncludesProcessStateData = includeProcessStateData;
+ mIncludesScreenStateData = includeScreenStateData;
+ mIncludesPowerStateData = includesPowerStateData;
mMinConsumedPowerThreshold = minConsumedPowerThreshold;
for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
final BatteryConsumer.BatteryConsumerData data =
@@ -869,6 +941,14 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
return mIncludesProcessStateData;
}
+ public boolean isScreenStateDataNeeded() {
+ return mIncludesScreenStateData;
+ }
+
+ public boolean isPowerStateDataNeeded() {
+ return mIncludesPowerStateData;
+ }
+
/**
* Returns true if this Builder is configured to hold data for the specified
* custom power component ID.
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 203ef47d857e..d0ed297d2bda 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -73,6 +73,10 @@ public final class BatteryUsageStatsQuery implements Parcelable {
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010;
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE = 0x0020;
+
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE = 0x0040;
+
private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
private final int mFlags;
@@ -123,6 +127,14 @@ public final class BatteryUsageStatsQuery implements Parcelable {
return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
}
+ public boolean isScreenStateDataNeeded() {
+ return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0;
+ }
+
+ public boolean isPowerStateDataNeeded() {
+ return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
+ }
+
/**
* Returns the power components that should be estimated or null if all power components
* are being requested.
@@ -297,6 +309,24 @@ public final class BatteryUsageStatsQuery implements Parcelable {
}
/**
+ * Requests that screen state data (screen-on, screen-other) be included in the
+ * BatteryUsageStats, if available.
+ */
+ public Builder includeScreenStateData() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE;
+ return this;
+ }
+
+ /**
+ * Requests that power state data (on-battery, power-other) be included in the
+ * BatteryUsageStats, if available.
+ */
+ public Builder includePowerStateData() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE;
+ return this;
+ }
+
+ /**
* Requests to aggregate stored snapshots between the two supplied timestamps
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index c9f207cf26e8..50242bad191b 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -29,7 +29,7 @@ import java.io.FileDescriptor;
* interface describes the abstract protocol for interacting with a
* remotable object. Do not implement this interface directly, instead
* extend from {@link Binder}.
- *
+ *
* <p>The key IBinder API is {@link #transact transact()} matched by
* {@link Binder#onTransact Binder.onTransact()}. These
* methods allow you to send a call to an IBinder object and receive a
@@ -40,7 +40,7 @@ import java.io.FileDescriptor;
* expected behavior when calling an object that exists in the local
* process, and the underlying inter-process communication (IPC) mechanism
* ensures that these same semantics apply when going across processes.
- *
+ *
* <p>The data sent through transact() is a {@link Parcel}, a generic buffer
* of data that also maintains some meta-data about its contents. The meta
* data is used to manage IBinder object references in the buffer, so that those
@@ -51,7 +51,7 @@ import java.io.FileDescriptor;
* same IBinder object back. These semantics allow IBinder/Binder objects to
* be used as a unique identity (to serve as a token or for other purposes)
* that can be managed across processes.
- *
+ *
* <p>The system maintains a pool of transaction threads in each process that
* it runs in. These threads are used to dispatch all
* IPCs coming in from other processes. For example, when an IPC is made from
@@ -62,7 +62,7 @@ import java.io.FileDescriptor;
* thread in process A returns to allow its execution to continue. In effect,
* other processes appear to use as additional threads that you did not create
* executing in your own process.
- *
+ *
* <p>The Binder system also supports recursion across processes. For example
* if process A performs a transaction to process B, and process B while
* handling that transaction calls transact() on an IBinder that is implemented
@@ -70,7 +70,7 @@ import java.io.FileDescriptor;
* transaction to finish will take care of calling Binder.onTransact() on the
* object being called by B. This ensures that the recursion semantics when
* calling remote binder object are the same as when calling local objects.
- *
+ *
* <p>When working with remote objects, you often want to find out when they
* are no longer valid. There are three ways this can be determined:
* <ul>
@@ -83,7 +83,7 @@ import java.io.FileDescriptor;
* a {@link DeathRecipient} with the IBinder, which will be called when its
* containing process goes away.
* </ul>
- *
+ *
* @see Binder
*/
public interface IBinder {
@@ -95,17 +95,17 @@ public interface IBinder {
* The last transaction code available for user commands.
*/
int LAST_CALL_TRANSACTION = 0x00ffffff;
-
+
/**
* IBinder protocol transaction code: pingBinder().
*/
int PING_TRANSACTION = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
-
+
/**
* IBinder protocol transaction code: dump internal state.
*/
int DUMP_TRANSACTION = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
-
+
/**
* IBinder protocol transaction code: execute a shell command.
* @hide
@@ -129,7 +129,7 @@ public interface IBinder {
* across the platform. To support older code, the default implementation
* logs the tweet to the main log as a simple emulation of broadcasting
* it publicly over the Internet.
- *
+ *
* <p>Also, upon completing the dispatch, the object must make a cup
* of tea, return it to the caller, and exclaim "jolly good message
* old boy!".
@@ -142,7 +142,7 @@ public interface IBinder {
* its own like counter, and may display this value to the user to indicate the
* quality of the app. This is an optional command that applications do not
* need to handle, so the default implementation is to do nothing.
- *
+ *
* <p>There is no response returned and nothing about the
* system will be functionally affected by it, but it will improve the
* app's self-esteem.
@@ -185,7 +185,8 @@ public interface IBinder {
/**
* Limit that should be placed on IPC sizes to keep them safely under the
- * transaction buffer limit.
+ * transaction buffer limit. This is a recommendation, and is not the real
+ * limit. Transactions should be preferred to be even smaller than this.
* @hide
*/
public static final int MAX_IPC_SIZE = 64 * 1024;
@@ -206,7 +207,7 @@ public interface IBinder {
/**
* Check to see if the object still exists.
- *
+ *
* @return Returns false if the
* hosting process is gone, otherwise the result (always by default
* true) returned by the pingBinder() implementation on the other
@@ -221,7 +222,7 @@ public interface IBinder {
* true, the process may have died while the call is returning.
*/
public boolean isBinderAlive();
-
+
/**
* Attempt to retrieve a local implementation of an interface
* for this Binder object. If null is returned, you will need
@@ -232,7 +233,7 @@ public interface IBinder {
/**
* Print the object's state into the given stream.
- *
+ *
* @param fd The raw file descriptor that the dump is being sent to.
* @param args additional arguments to the dump request.
*/
@@ -280,7 +281,7 @@ public interface IBinder {
/**
* Perform a generic operation with the object.
- *
+ *
* @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
@@ -360,13 +361,13 @@ public interface IBinder {
* Remove a previously registered death notification.
* The recipient will no longer be called if this object
* dies.
- *
+ *
* @return {@code true} if the <var>recipient</var> is successfully
* unlinked, assuring you that its
* {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
* will not be called; {@code false} if the target IBinder has already
* died, meaning the method has been (or soon will be) called.
- *
+ *
* @throws java.util.NoSuchElementException if the given
* <var>recipient</var> has not been registered with the IBinder, and
* the IBinder is still alive. Note that if the <var>recipient</var>
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index a49ee7d00751..0c34c6fea1d0 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -110,92 +110,6 @@ interface INetworkManagementService
void shutdown();
/**
- ** TETHERING RELATED
- **/
-
- /**
- * Returns true if IP forwarding is enabled
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Use {@code android.net.INetd#ipfwdEnabled}")
- boolean getIpForwardingEnabled();
-
- /**
- * Enables/Disables IP Forwarding
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
- + "{@code android.net.TetheringManager#startTethering}. See also "
- + "{@code INetd#ipfwdEnableForwarding(String)}.")
- void setIpForwardingEnabled(boolean enabled);
-
- /**
- * Start tethering services with the specified dhcp server range
- * arg is a set of start end pairs defining the ranges.
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "{@code android.net.TetheringManager#startTethering}")
- void startTethering(in String[] dhcpRanges);
-
- /**
- * Stop currently running tethering services
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "{@code android.net.TetheringManager#stopTethering(int)}")
- void stopTethering();
-
- /**
- * Returns true if tethering services are started
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Generally track your own tethering requests. "
- + "See also {@code android.net.INetd#tetherIsEnabled()}")
- boolean isTetheringStarted();
-
- /**
- * Tethers the specified interface
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
- + "{@code android.net.TetheringManager#startTethering}. See also "
- + "{@code com.android.net.module.util.NetdUtils#tetherInterface}.")
- void tetherInterface(String iface);
-
- /**
- * Untethers the specified interface
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Avoid using this directly. Instead, disable "
- + "tethering with {@code android.net.TetheringManager#stopTethering(int)}. "
- + "See also {@code NetdUtils#untetherInterface}.")
- void untetherInterface(String iface);
-
- /**
- * Returns a list of currently tethered interfaces
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "{@code android.net.TetheringManager#getTetheredIfaces()}")
- String[] listTetheredInterfaces();
-
- /**
- * Enables Network Address Translation between two interfaces.
- * The address and netmask of the external interface is used for
- * the NAT'ed network.
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
- + "{@code android.net.TetheringManager#startTethering}.")
- void enableNat(String internalInterface, String externalInterface);
-
- /**
- * Disables Network Address Translation between two interfaces.
- */
- @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
- publicAlternatives = "Avoid using this directly. Instead, disable tethering with "
- + "{@code android.net.TetheringManager#stopTethering(int)}.")
- void disableNat(String internalInterface, String externalInterface);
-
- /**
** DATA USAGE RELATED
**/
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index b035f123d5db..f22e1eac9643 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -17,8 +17,12 @@ package android.os;
import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
+import static android.os.BatteryConsumer.POWER_STATE_ANY;
+import static android.os.BatteryConsumer.POWER_STATE_UNSPECIFIED;
import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+import static android.os.BatteryConsumer.SCREEN_STATE_ANY;
+import static android.os.BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
import android.annotation.NonNull;
@@ -56,23 +60,100 @@ class PowerComponents {
* Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
*/
public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
- if (dimensions.powerComponent != POWER_COMPONENT_ANY) {
- return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
- dimensions.processState).mPowerColumnIndex);
- } else if (dimensions.processState != PROCESS_STATE_ANY) {
- if (!mData.layout.processStateDataIncluded) {
- throw new IllegalArgumentException(
- "No data included in BatteryUsageStats for " + dimensions);
+ return getConsumedPower(dimensions.powerComponent, dimensions.processState,
+ dimensions.screenState, dimensions.powerState);
+ }
+
+ /**
+ * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
+ */
+ public double getConsumedPower(@BatteryConsumer.PowerComponent int powerComponent,
+ @BatteryConsumer.ProcessState int processState,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState) {
+ if (powerComponent == POWER_COMPONENT_ANY && processState == PROCESS_STATE_ANY
+ && screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) {
+ return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
+ }
+
+ if (powerComponent != POWER_COMPONENT_ANY
+ && ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY)
+ || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY))) {
+ BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
+ processState, screenState, powerState);
+ if (key != null) {
+ return mData.getDouble(key.mPowerColumnIndex);
}
- final BatteryConsumer.Key[] keys =
- mData.layout.processStateKeys[dimensions.processState];
- double totalPowerMah = 0;
- for (int i = keys.length - 1; i >= 0; i--) {
- totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
+ return 0;
+ }
+
+ if (mData.layout.processStateDataIncluded || mData.layout.screenStateDataIncluded
+ || mData.layout.powerStateDataIncluded) {
+ double total = 0;
+ for (BatteryConsumer.Key key : mData.layout.keys) {
+ if (key.processState != PROCESS_STATE_UNSPECIFIED
+ && key.matches(powerComponent, processState, screenState, powerState)) {
+ total += mData.getDouble(key.mPowerColumnIndex);
+ }
}
- return totalPowerMah;
+ if (total != 0) {
+ return total;
+ }
+ }
+
+ BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState,
+ SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+ if (key != null) {
+ return mData.getDouble(key.mPowerColumnIndex);
} else {
- return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
+ return 0;
+ }
+ }
+
+ /**
+ * Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
+ */
+ public long getUsageDurationMillis(@NonNull BatteryConsumer.Dimensions dimensions) {
+ return getUsageDurationMillis(dimensions.powerComponent, dimensions.processState,
+ dimensions.screenState, dimensions.powerState);
+ }
+
+ /**
+ * Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
+ */
+ public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int powerComponent,
+ @BatteryConsumer.ProcessState int processState,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState) {
+ if ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY)
+ || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY)) {
+ BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
+ processState, screenState, powerState);
+ if (key != null) {
+ return mData.getLong(key.mDurationColumnIndex);
+ }
+ return 0;
+ }
+
+ if (mData.layout.screenStateDataIncluded || mData.layout.powerStateDataIncluded) {
+ long total = 0;
+ for (BatteryConsumer.Key key : mData.layout.keys) {
+ if (key.processState != PROCESS_STATE_UNSPECIFIED
+ && key.matches(powerComponent, processState, screenState, powerState)) {
+ total += mData.getLong(key.mDurationColumnIndex);
+ }
+ }
+ if (total != 0) {
+ return total;
+ }
+ }
+
+ BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState,
+ SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+ if (key != null) {
+ return mData.getLong(key.mDurationColumnIndex);
+ } else {
+ return 0;
}
}
@@ -84,7 +165,11 @@ class PowerComponents {
* @return Amount of consumed power in mAh.
*/
public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
- return mData.getDouble(key.mPowerColumnIndex);
+ if (mData.hasValue(key.mPowerColumnIndex)) {
+ return mData.getDouble(key.mPowerColumnIndex);
+ }
+ return getConsumedPower(key.powerComponent, key.processState, key.screenState,
+ key.powerState);
}
/**
@@ -135,7 +220,12 @@ class PowerComponents {
* @return Amount of time in milliseconds.
*/
public long getUsageDurationMillis(BatteryConsumer.Key key) {
- return mData.getLong(key.mDurationColumnIndex);
+ if (mData.hasValue(key.mDurationColumnIndex)) {
+ return mData.getLong(key.mDurationColumnIndex);
+ }
+
+ return getUsageDurationMillis(key.powerComponent, key.processState, key.screenState,
+ key.powerState);
}
/**
@@ -154,51 +244,77 @@ class PowerComponents {
}
}
- public void dump(PrintWriter pw, boolean skipEmptyComponents) {
- String separator = "";
+ void dump(PrintWriter pw, @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
StringBuilder sb = new StringBuilder();
-
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- for (BatteryConsumer.Key key: mData.getKeys(componentId)) {
- final double componentPower = getConsumedPower(key);
- final long durationMs = getUsageDurationMillis(key);
- if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
- continue;
+ dump(sb, componentId, PROCESS_STATE_ANY, screenState, powerState, skipEmptyComponents);
+ if (mData.layout.processStateDataIncluded) {
+ for (int processState = 0; processState < BatteryConsumer.PROCESS_STATE_COUNT;
+ processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+ dump(sb, componentId, processState, screenState, powerState,
+ skipEmptyComponents);
}
+ }
+ }
- sb.append(separator);
- separator = " ";
- sb.append(key.toShortString());
- sb.append("=");
- sb.append(BatteryStats.formatCharge(componentPower));
-
- if (durationMs != 0) {
- sb.append(" (");
- BatteryStats.formatTimeMsNoSpace(sb, durationMs);
- sb.append(")");
+ // TODO(b/352835319): take into account screen and power states
+ if (screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) {
+ final int customComponentCount = mData.layout.customPowerComponentCount;
+ for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customComponentCount;
+ customComponentId++) {
+ final double customComponentPower =
+ getConsumedPowerForCustomComponent(customComponentId);
+ if (skipEmptyComponents && customComponentPower == 0) {
+ continue;
}
+ sb.append(getCustomPowerComponentName(customComponentId));
+ sb.append("=");
+ sb.append(BatteryStats.formatCharge(customComponentPower));
+ sb.append(" ");
}
}
- final int customComponentCount = mData.layout.customPowerComponentCount;
- for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + customComponentCount;
- customComponentId++) {
- final double customComponentPower =
- getConsumedPowerForCustomComponent(customComponentId);
- if (skipEmptyComponents && customComponentPower == 0) {
- continue;
- }
- sb.append(separator);
- separator = " ";
- sb.append(getCustomPowerComponentName(customComponentId));
- sb.append("=");
- sb.append(BatteryStats.formatCharge(customComponentPower));
+ // Remove trailing spaces
+ while (!sb.isEmpty() && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+ sb.setLength(sb.length() - 1);
}
- pw.print(sb);
+ pw.println(sb);
+ }
+
+ private void dump(StringBuilder sb, @BatteryConsumer.PowerComponent int powerComponent,
+ @BatteryConsumer.ProcessState int processState,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
+ final double componentPower = getConsumedPower(powerComponent, processState, screenState,
+ powerState);
+ final long durationMs = getUsageDurationMillis(powerComponent, processState, screenState,
+ powerState);
+ if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
+ return;
+ }
+
+ sb.append(BatteryConsumer.powerComponentIdToString(powerComponent));
+ if (processState != PROCESS_STATE_UNSPECIFIED) {
+ sb.append(':');
+ sb.append(BatteryConsumer.processStateToString(processState));
+ }
+ sb.append("=");
+ sb.append(BatteryStats.formatCharge(componentPower));
+
+ if (durationMs != 0) {
+ sb.append(" (");
+ BatteryStats.formatTimeMsNoSpace(sb, durationMs);
+ sb.append(")");
+ }
+ sb.append(' ');
}
/** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
@@ -220,11 +336,13 @@ class PowerComponents {
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
-
- final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
+ final BatteryConsumer.Key[] keys = mData.layout.getKeys(componentId);
for (BatteryConsumer.Key key : keys) {
- final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
- final long durationMs = getUsageDurationMillis(key);
+ final long powerDeciCoulombs = convertMahToDeciCoulombs(
+ getConsumedPower(key.powerComponent, key.processState, key.screenState,
+ key.powerState));
+ final long durationMs = getUsageDurationMillis(key.powerComponent, key.processState,
+ key.screenState, key.powerState);
if (powerDeciCoulombs == 0 && durationMs == 0) {
// No interesting data. Make sure not to even write the COMPONENT int.
@@ -329,34 +447,43 @@ class PowerComponents {
void writeToXml(TypedXmlSerializer serializer) throws IOException {
serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
- for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
- final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
- for (BatteryConsumer.Key key : keys) {
- final double powerMah = getConsumedPower(key);
- final long durationMs = getUsageDurationMillis(key);
- if (powerMah == 0 && durationMs == 0) {
- continue;
- }
+ for (BatteryConsumer.Key key : mData.layout.keys) {
+ if (!mData.hasValue(key.mPowerColumnIndex)
+ && !mData.hasValue(key.mDurationColumnIndex)) {
+ continue;
+ }
- serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
- if (key.processState != PROCESS_STATE_UNSPECIFIED) {
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
- key.processState);
- }
- if (powerMah != 0) {
- serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
- }
- if (durationMs != 0) {
- serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
- }
- if (mData.layout.powerModelsIncluded) {
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
- getPowerModel(key));
- }
- serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+ final double powerMah = getConsumedPower(key);
+ final long durationMs = getUsageDurationMillis(key);
+ if (powerMah == 0 && durationMs == 0) {
+ continue;
+ }
+
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, key.powerComponent);
+ if (key.processState != PROCESS_STATE_UNSPECIFIED) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
+ key.processState);
+ }
+ if (key.screenState != SCREEN_STATE_UNSPECIFIED) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCREEN_STATE,
+ key.screenState);
+ }
+ if (key.powerState != POWER_STATE_UNSPECIFIED) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_POWER_STATE,
+ key.powerState);
+ }
+ if (powerMah != 0) {
+ serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
+ }
+ if (durationMs != 0) {
+ serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
+ }
+ if (mData.layout.powerModelsIncluded) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
+ getPowerModel(key));
}
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
}
final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
@@ -401,6 +528,8 @@ class PowerComponents {
case BatteryUsageStats.XML_TAG_COMPONENT: {
int componentId = -1;
int processState = PROCESS_STATE_UNSPECIFIED;
+ int screenState = SCREEN_STATE_UNSPECIFIED;
+ int powerState = POWER_STATE_UNSPECIFIED;
double powerMah = 0;
long durationMs = 0;
int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
@@ -412,6 +541,12 @@ class PowerComponents {
case BatteryUsageStats.XML_ATTR_PROCESS_STATE:
processState = parser.getAttributeInt(i);
break;
+ case BatteryUsageStats.XML_ATTR_SCREEN_STATE:
+ screenState = parser.getAttributeInt(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_POWER_STATE:
+ powerState = parser.getAttributeInt(i);
+ break;
case BatteryUsageStats.XML_ATTR_POWER:
powerMah = parser.getAttributeDouble(i);
break;
@@ -423,8 +558,8 @@ class PowerComponents {
break;
}
}
- final BatteryConsumer.Key key =
- builder.mData.getKey(componentId, processState);
+ final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId,
+ processState, screenState, powerState);
builder.setConsumedPower(key, powerMah, model);
builder.setUsageDurationMillis(key, durationMs);
break;
@@ -468,11 +603,9 @@ class PowerComponents {
Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
mData = data;
mMinConsumedPowerThreshold = minConsumedPowerThreshold;
- for (BatteryConsumer.Key[] keys : mData.layout.keys) {
- for (BatteryConsumer.Key key : keys) {
- if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
- mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
- }
+ for (BatteryConsumer.Key key : mData.layout.keys) {
+ if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
+ mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
}
}
}
@@ -572,51 +705,41 @@ class PowerComponents {
+ ", expected: " + mData.layout.customPowerComponentCount);
}
- for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0;
- componentId--) {
- final BatteryConsumer.Key[] keys = mData.layout.keys[componentId];
- for (BatteryConsumer.Key key: keys) {
- BatteryConsumer.Key otherKey = null;
- for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) {
- if (aKey.equals(key)) {
- otherKey = aKey;
- break;
- }
- }
-
- if (otherKey == null) {
- continue;
- }
+ for (BatteryConsumer.Key key : mData.layout.keys) {
+ BatteryConsumer.Key otherKey = otherData.layout.getKey(key.powerComponent,
+ key.processState, key.screenState, key.powerState);
+ if (otherKey == null) {
+ continue;
+ }
- mData.putDouble(key.mPowerColumnIndex,
- mData.getDouble(key.mPowerColumnIndex)
- + otherData.getDouble(otherKey.mPowerColumnIndex));
- mData.putLong(key.mDurationColumnIndex,
- mData.getLong(key.mDurationColumnIndex)
- + otherData.getLong(otherKey.mDurationColumnIndex));
+ mData.putDouble(key.mPowerColumnIndex,
+ mData.getDouble(key.mPowerColumnIndex)
+ + otherData.getDouble(otherKey.mPowerColumnIndex));
+ mData.putLong(key.mDurationColumnIndex,
+ mData.getLong(key.mDurationColumnIndex)
+ + otherData.getLong(otherKey.mDurationColumnIndex));
- if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
- continue;
- }
+ if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
+ continue;
+ }
- boolean undefined = false;
- if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
+ boolean undefined = false;
+ if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
+ undefined = true;
+ } else {
+ final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
+ int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
+ if (powerModel == POWER_MODEL_UNINITIALIZED) {
+ mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
+ } else if (powerModel != otherPowerModel
+ && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
undefined = true;
- } else {
- final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
- int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
- if (powerModel == POWER_MODEL_UNINITIALIZED) {
- mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
- } else if (powerModel != otherPowerModel
- && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
- undefined = true;
- }
}
+ }
- if (undefined) {
- mData.putInt(key.mPowerModelColumnIndex,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
+ if (undefined) {
+ mData.putInt(key.mPowerModelColumnIndex,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
}
}
@@ -631,10 +754,8 @@ class PowerComponents {
final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
final int otherDurationColumnIndex =
otherData.layout.firstCustomUsageDurationColumn + i;
- mData.putLong(usageColumnIndex,
- mData.getLong(usageColumnIndex) + otherData.getLong(
- otherDurationColumnIndex)
- );
+ mData.putLong(usageColumnIndex, mData.getLong(usageColumnIndex)
+ + otherData.getLong(otherDurationColumnIndex));
}
}
@@ -647,7 +768,8 @@ class PowerComponents {
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
totalPowerMah += mData.getDouble(
- mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex);
+ mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_ANY, SCREEN_STATE_ANY,
+ POWER_STATE_ANY).mPowerColumnIndex);
}
for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
totalPowerMah += mData.getDouble(
@@ -661,19 +783,17 @@ class PowerComponents {
*/
@NonNull
public PowerComponents build() {
- for (BatteryConsumer.Key[] keys : mData.layout.keys) {
- for (BatteryConsumer.Key key : keys) {
- if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
- if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
- mData.putInt(key.mPowerModelColumnIndex,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
+ for (BatteryConsumer.Key key: mData.layout.keys) {
+ if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
+ if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
+ mData.putInt(key.mPowerModelColumnIndex,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
}
+ }
- if (mMinConsumedPowerThreshold != 0) {
- if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
- mData.putDouble(key.mPowerColumnIndex, 0);
- }
+ if (mMinConsumedPowerThreshold != 0) {
+ if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
+ mData.putDouble(key.mPowerColumnIndex, 0);
}
}
}
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 0be2d3e30c33..e95c6a44c281 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -277,7 +277,7 @@ public final class ServiceManager {
if (service != null) {
return service;
} else {
- return Binder.allowBlocking(getIServiceManager().checkService(name));
+ return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder());
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
@@ -425,7 +425,7 @@ public final class ServiceManager {
private static IBinder rawGetService(String name) throws RemoteException {
final long start = sStatLogger.getTime();
- final IBinder binder = getIServiceManager().getService(name);
+ final IBinder binder = getIServiceManager().getService(name).getBinder();
final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7b91dd5822e7..6c9a5c7f9fff 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -58,12 +58,12 @@ class ServiceManagerProxy implements IServiceManager {
}
@UnsupportedAppUsage
- public IBinder getService(String name) throws RemoteException {
+ public Service getService(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return mServiceManager.checkService(name);
+ return checkService(name);
}
- public IBinder checkService(String name) throws RemoteException {
+ public Service checkService(String name) throws RemoteException {
return mServiceManager.checkService(name);
}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index b5029a6aaff3..2fde5e7eff6a 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -119,7 +119,7 @@
"PowerComponents\\.java",
"[^/]*BatteryConsumer[^/]*\\.java"
],
- "name": "BatteryUsageStatsProtoTests"
+ "name": "PowerStatsTests"
},
{
"file_patterns": ["SharedMemory[^/]*\\.java"],
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 53af838cb535..9b5a378d6591 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -140,12 +140,50 @@ public final class UidBatteryConsumer extends BatteryConsumer {
skipEmptyComponents);
appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_CACHED,
skipEmptyComponents);
- pw.print(sb);
+ pw.println(sb);
+ } else {
+ pw.println();
}
- pw.print(" ( ");
- mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */);
- pw.print(" ) ");
+ pw.print(" ");
+ mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, skipEmptyComponents);
+
+ if (mData.layout.powerStateDataIncluded || mData.layout.screenStateDataIncluded) {
+ for (int powerState = 0; powerState < POWER_STATE_COUNT; powerState++) {
+ if (mData.layout.powerStateDataIncluded && powerState == POWER_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) {
+ if (mData.layout.screenStateDataIncluded
+ && screenState == POWER_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ final double consumedPower = mPowerComponents.getConsumedPower(
+ POWER_COMPONENT_ANY,
+ PROCESS_STATE_ANY, screenState, powerState);
+ if (consumedPower == 0) {
+ continue;
+ }
+
+ pw.print(" (");
+ if (powerState != POWER_STATE_UNSPECIFIED) {
+ pw.print(BatteryConsumer.powerStateToString(powerState));
+ }
+ if (screenState != SCREEN_STATE_UNSPECIFIED) {
+ if (powerState != POWER_STATE_UNSPECIFIED) {
+ pw.print(", ");
+ }
+ pw.print("screen ");
+ pw.print(BatteryConsumer.screenStateToString(screenState));
+ }
+ pw.print(") ");
+ mPowerComponents.dump(pw, screenState, powerState,
+ skipEmptyComponents /* skipTotalPowerComponent */);
+ }
+ }
+ }
}
private void appendProcessStateData(StringBuilder sb, @ProcessState int processState,
@@ -160,10 +198,6 @@ public final class UidBatteryConsumer extends BatteryConsumer {
.append(BatteryStats.formatCharge(power));
}
- static UidBatteryConsumer create(BatteryConsumerData data) {
- return new UidBatteryConsumer(data);
- }
-
/** Serializes this object to XML */
void writeToXml(TypedXmlSerializer serializer) throws IOException {
if (getConsumedPower() == 0) {
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index 23ba0c635eca..ea2be7b80e94 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -60,10 +60,10 @@ public class UserBatteryConsumer extends BatteryConsumer {
pw.print("User ");
pw.print(getUserId());
pw.print(": ");
- pw.print(BatteryStats.formatCharge(consumedPower));
- pw.print(" ( ");
- mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */);
- pw.print(" ) ");
+ pw.println(BatteryStats.formatCharge(consumedPower));
+ pw.print(" ");
+ mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY,
+ skipEmptyComponents /* skipTotalPowerComponent */);
}
/** Serializes this object to XML */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2562c8e31095..ff389208a579 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11075,6 +11075,13 @@ public final class Settings {
public static final String MANDATORY_BIOMETRICS = "mandatory_biometrics";
/**
+ * Whether or not requirements for mandatory biometrics is satisfied.
+ * @hide
+ */
+ public static final String MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED =
+ "mandatory_biometrics_requirements_satisfied";
+
+ /**
* Whether or not active unlock triggers on wake.
* @hide
*/
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index e4fc1cd9dad0..fbeab84fa96d 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -787,7 +787,6 @@ public class DreamService extends Service implements Window.Callback {
*/
public void setInteractive(boolean interactive) {
mInteractive = interactive;
- updateAccessibilityMessage();
}
/**
@@ -1641,9 +1640,9 @@ public class DreamService extends Service implements Window.Callback {
if (mWindow == null) return;
if (mDreamAccessibility == null) {
final View rootView = mWindow.getDecorView();
- mDreamAccessibility = new DreamAccessibility(this, rootView);
+ mDreamAccessibility = new DreamAccessibility(this, rootView, this::wakeUp);
}
- mDreamAccessibility.updateAccessibilityConfiguration(isInteractive());
+ mDreamAccessibility.updateAccessibilityConfiguration();
}
private boolean getWindowFlagValue(int flag, boolean defaultValue) {
diff --git a/core/java/android/service/dreams/utils/DreamAccessibility.java b/core/java/android/service/dreams/utils/DreamAccessibility.java
index c38f41bab5a6..f504ff7d4650 100644
--- a/core/java/android/service/dreams/utils/DreamAccessibility.java
+++ b/core/java/android/service/dreams/utils/DreamAccessibility.java
@@ -18,6 +18,7 @@ package android.service.dreams.utils;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Bundle;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -32,22 +33,22 @@ public class DreamAccessibility {
private final Context mContext;
private final View mView;
private final View.AccessibilityDelegate mAccessibilityDelegate;
+ private final Runnable mDismissCallback;
- public DreamAccessibility(@NonNull Context context, @NonNull View view) {
+ public DreamAccessibility(@NonNull Context context, @NonNull View view,
+ @NonNull Runnable dismissCallback) {
mContext = context;
mView = view;
mAccessibilityDelegate = createNewAccessibilityDelegate(mContext);
+ mDismissCallback = dismissCallback;
}
/**
- * @param interactive
- * Removes and add accessibility configuration depending if the dream is interactive or not
+ * Adds default accessibility configuration if none exist on the dream
*/
- public void updateAccessibilityConfiguration(Boolean interactive) {
- if (!interactive) {
+ public void updateAccessibilityConfiguration() {
+ if (mView.getAccessibilityDelegate() == null) {
addAccessibilityConfiguration();
- } else {
- removeCustomAccessibilityAction();
}
}
@@ -58,31 +59,28 @@ public class DreamAccessibility {
mView.setAccessibilityDelegate(mAccessibilityDelegate);
}
- /**
- * Removes Configured the accessibility actions for the given root view.
- */
- private void removeCustomAccessibilityAction() {
- if (mView.getAccessibilityDelegate() == mAccessibilityDelegate) {
- mView.setAccessibilityDelegate(null);
- }
- }
-
private View.AccessibilityDelegate createNewAccessibilityDelegate(Context context) {
return new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- for (AccessibilityNodeInfo.AccessibilityAction action : info.getActionList()) {
- if (action.getId() == AccessibilityNodeInfo.ACTION_CLICK) {
- info.removeAction(action);
- break;
- }
- }
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLICK,
+ AccessibilityNodeInfo.ACTION_DISMISS,
context.getResources().getString(R.string.dream_accessibility_action_click)
));
}
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ switch(action){
+ case AccessibilityNodeInfo.ACTION_DISMISS:
+ if (mDismissCallback != null) {
+ mDismissCallback.run();
+ }
+ break;
+ }
+ return true;
+ }
};
}
}
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 5d84d17bdb6e..b07534f5fe52 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,4 +68,11 @@ public class ClientFlags {
public static boolean fixMisalignedContextMenu() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
}
+
+ /**
+ * @see Flags#clearFontVariationSettings()
+ */
+ public static boolean clearFontVariationSettings() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
+ }
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9e02460d2637..4dca284b8a4d 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,6 +61,7 @@ public final class TextFlags {
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
Flags.FLAG_ICU_BIDI_MIGRATION,
Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
+ Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
};
/**
@@ -75,6 +76,7 @@ public final class TextFlags {
Flags.fixLineHeightForLocale(),
Flags.icuBidiMigration(),
Flags.fixMisalignedContextMenu(),
+ Flags.clearFontVariationSettings(),
};
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 8836c8a3a113..02c63db2c8a6 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -220,3 +220,23 @@ flag {
is_fixed_read_only: true
bug: "346915432"
}
+
+flag {
+ name: "clear_font_variation_settings"
+ namespace: "text"
+ description: "The font variation settings must be cleared when the new Typeface is set"
+ bug: "353609778"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "portuguese_hyphenator"
+ namespace: "text"
+ description: "Portuguese taiored hyphenator"
+ bug: "344656282"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/text/format/DateIntervalFormat.java b/core/java/android/text/format/DateIntervalFormat.java
index e8236fda42b6..8dea3228eb0c 100644
--- a/core/java/android/text/format/DateIntervalFormat.java
+++ b/core/java/android/text/format/DateIntervalFormat.java
@@ -26,6 +26,7 @@ import android.icu.util.ULocale;
import android.util.LruCache;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.libcore.Flags;
import java.text.FieldPosition;
import java.util.TimeZone;
@@ -123,4 +124,14 @@ public final class DateIntervalFormat {
&& c.get(Calendar.SECOND) == 0
&& c.get(Calendar.MILLISECOND) == 0;
}
+
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public static boolean isLibcoreVFlagEnabled() {
+ // Note that the Flags class is expected to be jarjar-ed in the build-time.
+ // See go/repackage_flags
+ // The full-qualified name should be like
+ // com.android.internal.hidden_from_bootclasspath.com.android.libcore.Flags
+ return Flags.vApis();
+ }
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 1d950dc44e46..6343313b2e01 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -119,9 +119,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
@Override
public boolean applyLocalVisibilityOverride() {
- ImeTracing.getInstance().triggerClientDump(
- "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
- mController.getHost().getInputMethodManager(), null /* icProto */);
+ if (!Flags.refactorInsetsController()) {
+ ImeTracing.getInstance().triggerClientDump(
+ "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
+ mController.getHost().getInputMethodManager(), null /* icProto */);
+ }
return super.applyLocalVisibilityOverride();
}
@@ -205,9 +207,13 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
@Override
public void removeSurface() {
- final IBinder window = mController.getHost().getWindowToken();
- if (window != null) {
- getImm().removeImeSurface(window);
+ if (Flags.refactorInsetsController()) {
+ super.removeSurface();
+ } else {
+ final IBinder window = mController.getHost().getWindowToken();
+ if (window != null) {
+ getImm().removeImeSurface(window);
+ }
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index df2af731037e..f166b89a1d13 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -765,7 +765,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public InsetsController(Host host) {
this(host, (controller, id, type) -> {
- if (type == ime()) {
+ if (!Flags.refactorInsetsController() && type == ime()) {
return new ImeInsetsSourceConsumer(id, controller.mState,
Transaction::new, controller);
} else {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c73cbc6e9a57..477e35b6e655 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -43,6 +43,7 @@ import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.ImeTracing;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -296,6 +297,13 @@ public class InsetsSourceConsumer {
@VisibleForTesting(visibility = PACKAGE)
public boolean applyLocalVisibilityOverride() {
+ if (Flags.refactorInsetsController()) {
+ if (mType == WindowInsets.Type.ime()) {
+ ImeTracing.getInstance().triggerClientDump(
+ "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
+ mController.getHost().getInputMethodManager(), null /* icProto */);
+ }
+ }
final InsetsSource source = mState.peekSource(mId);
if (source == null) {
return false;
@@ -396,6 +404,14 @@ public class InsetsSourceConsumer {
*/
public void removeSurface() {
// no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
+ if (Flags.refactorInsetsController()) {
+ if (mType == WindowInsets.Type.ime()) {
+ final IBinder window = mController.getHost().getWindowToken();
+ if (window != null) {
+ mController.getHost().getInputMethodManager().removeImeSurface(window);
+ }
+ }
+ }
}
@VisibleForTesting(visibility = PACKAGE)
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fedbe4a65e07..42d66ce6bf1b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -366,13 +366,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
Log.e(TAG, "Received invalid input event");
return;
}
- try {
- vri.processingBackKey(true);
- vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
- true /* processImmediately */);
- } finally {
- vri.processingBackKey(false);
- }
+ vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+ true /* processImmediately */);
});
}
};
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 596726f83c15..2f204f9b1be9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -727,8 +727,6 @@ public final class ViewRootImpl implements ViewParent,
boolean mUpcomingWindowFocus;
@GuardedBy("this")
boolean mUpcomingInTouchMode;
- // While set, allow this VRI to handle back key without drop it.
- private boolean mProcessingBackKey;
/**
* Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back
* key event host app.
@@ -1135,6 +1133,8 @@ public final class ViewRootImpl implements ViewParent,
// Take 24 and 30 as an example, 24 is not a divisor of 30.
// We consider there is a conflict.
private boolean mIsFrameRateConflicted = false;
+ // Used to check whether SurfaceControl has been replaced.
+ private boolean mSurfaceReplaced = false;
// Used to set frame rate compatibility.
@Surface.FrameRateCompatibility int mFrameRateCompatibility =
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
@@ -3831,6 +3831,7 @@ public final class ViewRootImpl implements ViewParent,
surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
|| surfaceControlChanged) && mSurface.isValid();
if (surfaceReplaced) {
+ mSurfaceReplaced = true;
mSurfaceSequenceId++;
}
if (alwaysConsumeSystemBarsChanged) {
@@ -4443,6 +4444,7 @@ public final class ViewRootImpl implements ViewParent,
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ mSurfaceReplaced = false;
} else if (mPreferredFrameRate == 0) {
// From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
setPreferredFrameRate(0);
@@ -7265,7 +7267,7 @@ public final class ViewRootImpl implements ViewParent,
// Find a reason for dropping or canceling the event.
final String reason;
// The embedded window is focused, allow this VRI to handle back key.
- if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent))
+ if (!mAttachInfo.mHasWindowFocus && !isBack(q.mEvent)
&& !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
&& !isAutofillUiShowing()) {
// This is a non-pointer event and the window doesn't currently have input focus
@@ -7543,7 +7545,6 @@ public final class ViewRootImpl implements ViewParent,
animationCallback.onBackCancelled();
} else {
topCallback.onBackInvoked();
- return FINISH_HANDLED;
}
break;
}
@@ -7551,14 +7552,16 @@ public final class ViewRootImpl implements ViewParent,
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
if (!keyEvent.isCanceled()) {
topCallback.onBackInvoked();
- return FINISH_HANDLED;
} else {
Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true");
}
}
}
-
- return FINISH_NOT_HANDLED;
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ // forward a cancelled event so that following stages cancel their back logic
+ keyEvent.cancel();
+ }
+ return FORWARD;
}
@Override
@@ -11213,11 +11216,6 @@ public final class ViewRootImpl implements ViewParent,
mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget();
}
- // Make this VRI able to process back key without drop it.
- void processingBackKey(boolean processing) {
- mProcessingBackKey = processing;
- }
-
/**
* Collect and include any ScrollCaptureCallback instances registered with the window.
*
@@ -12549,15 +12547,8 @@ public final class ViewRootImpl implements ViewParent,
* @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true)
*/
public boolean injectBackKeyEvents(boolean preImeOnly) {
- boolean consumed;
- try {
- processingBackKey(true);
- sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly);
- consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly);
- } finally {
- processingBackKey(false);
- }
- return consumed;
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly);
+ return sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly);
}
private boolean sendBackKeyEvent(int action, boolean preImeOnly) {
@@ -12933,8 +12924,9 @@ public final class ViewRootImpl implements ViewParent,
boolean traceFrameRateCategory = false;
try {
- if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
- && mLastPreferredFrameRateCategory != frameRateCategory) {
+ if ((frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
+ && mLastPreferredFrameRateCategory != frameRateCategory)
+ || mSurfaceReplaced) {
traceFrameRateCategory = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
if (traceFrameRateCategory) {
String reason = reasonToString(frameRateReason);
@@ -12998,7 +12990,7 @@ public final class ViewRootImpl implements ViewParent,
boolean traceFrameRate = false;
try {
- if (mLastPreferredFrameRate != preferredFrameRate) {
+ if (mLastPreferredFrameRate != preferredFrameRate || mSurfaceReplaced) {
traceFrameRate = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
if (traceFrameRate) {
Trace.traceBegin(
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 561d979f2f9d..987c8c8213f3 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1708,6 +1708,7 @@ public final class WindowInsets {
}
mTypeBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
}
+ mSystemInsetsConsumed = false;
return this;
}
@@ -1736,6 +1737,7 @@ public final class WindowInsets {
}
mTypeMaxBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
}
+ mStableInsetsConsumed = false;
return this;
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a5ba294d6a19..90cfcb1e64a8 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,6 +982,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private long mParentNodeId = UNDEFINED_NODE_ID;
private long mLabelForId = UNDEFINED_NODE_ID;
private long mLabeledById = UNDEFINED_NODE_ID;
+ private LongArray mLabeledByIds;
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
@@ -3599,6 +3600,131 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Adds the view which serves as the label of the view represented by
+ * this info for accessibility purposes. When more than one labels are
+ * added, the content from each label is combined in the order that
+ * they are added.
+ * <p>
+ * If visible text can be used to describe or give meaning to this UI,
+ * this method is preferred. For example, a TextView before an EditText
+ * in the UI usually specifies what information is contained in the
+ * EditText. Hence, the EditText is labelled by the TextView.
+ * </p>
+ *
+ * @param label A view that labels this node's source.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public void addLabeledBy(@NonNull View label) {
+ addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Adds the view which serves as the label of the view represented by
+ * this info for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the label. When more than one
+ * labels are added, the content from each label is combined in the order
+ * that they are added.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * If visible text can be used to describe or give meaning to this UI,
+ * this method is preferred. For example, a TextView before an EditText
+ * in the UI usually specifies what information is contained in the
+ * EditText. Hence, the EditText is labelled by the TextView.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root A root whose virtual descendant labels this node's source.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
+ enforceNotSealed();
+ Preconditions.checkNotNull(root, "%s must not be null", root);
+ if (mLabeledByIds == null) {
+ mLabeledByIds = new LongArray();
+ }
+ mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
+ mLabeledByIds.add(mLabeledById);
+ }
+
+ /**
+ * Gets the list of node infos which serve as the labels of the view represented by
+ * this info for accessibility purposes.
+ *
+ * @return The list of labels in the order that they were added.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
+ enforceSealed();
+ List<AccessibilityNodeInfo> labels = new ArrayList<>();
+ if (mLabeledByIds == null) {
+ return labels;
+ }
+ for (int i = 0; i < mLabeledByIds.size(); i++) {
+ labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
+ }
+ return labels;
+ }
+
+ /**
+ * Removes a label. If the label was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param label The node which serves as this node's label.
+ * @return true if the label was present
+ * @see #addLabeledBy(View)
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public boolean removeLabeledBy(@NonNull View label) {
+ return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Removes a virtual label which is a descendant of the given
+ * <code>root</code>. If the label was not previously added to the node,
+ * calling this method has no effect.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual node which serves as this node's label.
+ * @return true if the label was present
+ * @see #addLabeledBy(View, int)
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final LongArray labeledByIds = mLabeledByIds;
+ if (labeledByIds == null) {
+ return false;
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ if (mLabeledById == labeledById) {
+ mLabeledById = UNDEFINED_NODE_ID;
+ }
+ final int index = labeledByIds.indexOf(labeledById);
+ if (index < 0) {
+ return false;
+ }
+ labeledByIds.remove(index);
+ return true;
+ }
+
+ /**
* Sets the view which serves as the label of the view represented by
* this info for accessibility purposes.
*
@@ -3631,7 +3757,17 @@ public class AccessibilityNodeInfo implements Parcelable {
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ if (Flags.supportMultipleLabeledby()) {
+ if (mLabeledByIds == null) {
+ mLabeledByIds = new LongArray();
+ } else {
+ mLabeledByIds.clear();
+ }
+ }
mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ if (Flags.supportMultipleLabeledby()) {
+ mLabeledByIds.add(mLabeledById);
+ }
}
/**
@@ -4242,6 +4378,12 @@ public class AccessibilityNodeInfo implements Parcelable {
fieldIndex++;
if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
+ if (Flags.supportMultipleLabeledby()) {
+ if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ }
if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4383,6 +4525,20 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+ if (Flags.supportMultipleLabeledby()) {
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final LongArray labeledByIds = mLabeledByIds;
+ if (labeledByIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int labeledByIdsSize = labeledByIds.size();
+ parcel.writeInt(labeledByIdsSize);
+ for (int i = 0; i < labeledByIdsSize; i++) {
+ parcel.writeLong(labeledByIds.get(i));
+ }
+ }
+ }
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4550,6 +4706,9 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = other.mParentNodeId;
mLabelForId = other.mLabelForId;
mLabeledById = other.mLabeledById;
+ if (Flags.supportMultipleLabeledby()) {
+ mLabeledByIds = other.mLabeledByIds;
+ }
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4656,6 +4815,20 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+ if (Flags.supportMultipleLabeledby()) {
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int labeledByIdsSize = parcel.readInt();
+ if (labeledByIdsSize <= 0) {
+ mLabeledByIds = null;
+ } else {
+ mLabeledByIds = new LongArray(labeledByIdsSize);
+ for (int i = 0; i < labeledByIdsSize; i++) {
+ final long labeledById = parcel.readLong();
+ mLabeledByIds.add(labeledById);
+ }
+ }
+ }
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 44c1acc34273..ed2bf79c51f4 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -184,6 +184,13 @@ flag {
}
flag {
+ name: "support_multiple_labeledby"
+ namespace: "accessibility"
+ description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
+ bug: "333780959"
+}
+
+flag {
name: "support_system_pinch_zoom_opt_out_apis"
namespace: "accessibility"
description: "Feature flag for declaring system pinch zoom opt-out apis"
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 5b1c7d54ddb7..0ab51e45a951 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -563,7 +563,7 @@ public class AutofillFeatureFlags {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELAYOUT,
- true);
+ false);
}
/** @hide */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c9d2eecfb9b0..fed8eea97688 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1352,12 +1352,16 @@ public final class InputMethodManager {
case MSG_SET_VISIBILITY:
final boolean visible = msg.arg1 != 0;
synchronized (mH) {
- if (visible) {
- showSoftInput(mServedView, /* flags */ 0);
- } else {
- if (mCurRootView != null
- && mCurRootView.getInsetsController() != null) {
- mCurRootView.getInsetsController().hide(WindowInsets.Type.ime());
+ if (mCurRootView != null) {
+ final var insetsController = mCurRootView.getInsetsController();
+ if (insetsController != null) {
+ if (visible) {
+ insetsController.show(WindowInsets.Type.ime(),
+ false /* fromIme */, null /* statsToken */);
+ } else {
+ insetsController.hide(WindowInsets.Type.ime(),
+ false /* fromIme */, null /* statsToken */);
+ }
}
}
}
@@ -2334,16 +2338,18 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
if (Flags.refactorInsetsController()) {
+ final var viewRootImpl = view.getViewRootImpl();
// In case of a running show IME animation, it should not be requested visible,
// otherwise the animation would jump and not be controlled by the user anymore
- if ((mCurRootView.getInsetsController().computeUserAnimatingTypes()
- & WindowInsets.Type.ime()) == 0) {
+ if (viewRootImpl != null
+ && (viewRootImpl.getInsetsController().computeUserAnimatingTypes()
+ & WindowInsets.Type.ime()) == 0) {
// TODO(b/322992891) handle case of SHOW_IMPLICIT
- view.getWindowInsetsController().show(WindowInsets.Type.ime());
+ viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(),
+ false /* fromIme */, statsToken);
return true;
- } else {
- return false;
}
+ return false;
} else {
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2497,7 +2503,10 @@ public final class InputMethodManager {
if (Flags.refactorInsetsController()) {
// TODO(b/322992891) handle case of HIDE_IMPLICIT_ONLY
- servedView.getWindowInsetsController().hide(WindowInsets.Type.ime());
+ final var viewRootImpl = servedView.getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime());
+ }
return true;
} else {
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 4c8bad6d0aff..205f1defccd1 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -412,8 +412,7 @@ public class SnapshotDrawerUtils {
final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch()
? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams;
final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
- final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
- if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
+ if (attrs == null || mainWindowParams == null) {
Log.w(TAG, "unable to create taskSnapshot surface ");
return null;
}
@@ -456,7 +455,10 @@ public class SnapshotDrawerUtils {
return layoutParams;
}
- static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+ static Rect getSystemBarInsets(Rect frame, @Nullable InsetsState state) {
+ if (state == null) {
+ return new Rect();
+ }
return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
false /* ignoreVisibility */).toRect();
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 48fb2b3ab129..f739622a1b92 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -98,6 +98,13 @@ flag {
}
flag {
+ name: "scrolling_from_letterbox"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether to enable app scrolling from gestures from letterbox area"
+ bug: "353697519"
+}
+
+flag {
name: "app_compat_refactoring"
namespace: "large_screen_experiences_app_compat"
description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 90245699c824..91ac4ff05687 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -101,6 +101,13 @@ flag {
}
flag {
+ name: "enable_cascading_windows"
+ namespace: "lse_desktop_experience"
+ description: "Whether to apply cascading effect for placing multiple windows when first launched"
+ bug: "325240051"
+}
+
+flag {
name: "enable_camera_compat_for_desktop_windowing"
namespace: "lse_desktop_experience"
description: "Whether to apply Camera Compat treatment to fixed-orientation apps in desktop windowing mode"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 5397e91bd249..c451cc880a8c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -19,6 +19,16 @@ flag {
}
flag {
+ name: "do_not_skip_ime_by_target_visibility"
+ namespace: "windowing_frontend"
+ description: "Avoid window traversal missing IME"
+ bug: "339375944"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "apply_lifecycle_on_pip_change"
namespace: "windowing_frontend"
description: "Make pip activity lifecyle change with windowing mode"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ae9d757e1e0b..13d465f183c9 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -3,16 +3,6 @@ container: "system"
# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
-# Using a fixed read only flag because there are ClientTransaction scheduling before
-# WindowManagerService creation.
-flag {
- namespace: "windowing_sdk"
- name: "bundle_client_transaction_flag"
- description: "To bundle multiple ClientTransactionItems into one ClientTransaction"
- bug: "260873529"
- is_fixed_read_only: true
-}
-
flag {
namespace: "windowing_sdk"
name: "activity_embedding_overlay_presentation_flag"
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 75ddb58590a1..f9c294758bf0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -48,6 +48,7 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
import android.text.TextUtils;
@@ -151,7 +152,8 @@ public class AccessibilityShortcutController {
* info for toggling a framework feature
*/
public static Map<ComponentName, FrameworkFeatureInfo>
- getFrameworkShortcutFeaturesMap() {
+ getFrameworkShortcutFeaturesMap() {
+
if (sFrameworkShortcutFeaturesMap == null) {
Map<ComponentName, FrameworkFeatureInfo> featuresMap = new ArrayMap<>(4);
featuresMap.put(COLOR_INVERSION_COMPONENT_NAME,
@@ -172,7 +174,7 @@ public class AccessibilityShortcutController {
R.string.one_handed_mode_feature_name));
}
featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
- new ToggleableFrameworkFeatureInfo(
+ new ExtraDimFrameworkFeatureInfo(
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
"1" /* Value to enable */, "0" /* Value to disable */,
R.string.reduce_bright_colors_feature_name));
@@ -828,6 +830,44 @@ public class AccessibilityShortcutController {
}
}
+
+ public static class ExtraDimFrameworkFeatureInfo extends FrameworkFeatureInfo {
+ ExtraDimFrameworkFeatureInfo(String settingKey, String settingOnValue,
+ String settingOffValue, int labelStringResourceId) {
+ super(settingKey, settingOnValue, settingOffValue, labelStringResourceId);
+ }
+
+ /**
+ * Perform shortcut action.
+ *
+ * @return True if the accessibility service is enabled, false otherwise.
+ */
+ public boolean activateShortcut(Context context, int userId) {
+ if (com.android.server.display.feature.flags.Flags.evenDimmer()
+ && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled)) {
+ launchExtraDimDialog();
+ return true;
+ } else {
+ // Assuming that the default state will be to have the feature off
+ final SettingsStringUtil.SettingStringHelper
+ setting = new SettingsStringUtil.SettingStringHelper(
+ context.getContentResolver(), getSettingKey(), userId);
+ if (!TextUtils.equals(getSettingOnValue(), setting.read())) {
+ setting.write(getSettingOnValue());
+ return true;
+ } else {
+ setting.write(getSettingOffValue());
+ return false;
+ }
+ }
+ }
+
+ private void launchExtraDimDialog() {
+ // TODO: launch Extra dim dialog for feature migration
+ }
+ }
+
// Class to allow mocking of static framework calls
public static class FrameworkObjectProvider {
public AccessibilityManager getAccessibilityManagerInstance(Context context) {
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 3e6f18e8e24b..69d1cb34005d 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -161,12 +161,12 @@ public class Cuj {
public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
/**
- * Track entering desktop mode interaction via app handle drag.
+ * Track app handle drag and hold interaction.
*
* <p>Tracking starts when the app handle is dragged and
- * finishes when the window animation to desktop ends after app handle release.
+ * finishes immediately after app handle release, before starting a new transition.
*/
- public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG = 107;
+ public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD = 107;
/** Track exiting desktop mode interaction. */
public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108;
@@ -197,8 +197,21 @@ public class Cuj {
/** Track launching an app through the Launcher Keyboard Quick Switch View */
public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 115;
+ /**
+ * Track entering desktop mode interaction via app handle drag release.
+ *
+ * <p>Tracking starts when the app handle is released and
+ * finishes when one of the three possible animations end:
+ * <ul>
+ * <li>release to desktop</li>
+ * <li>release to split-screen</li>
+ * <li>release to back to full-screen</li>
+ * </ul>.
+ */
+ public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE = 116;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
/** @hide */
@IntDef({
@@ -297,7 +310,7 @@ public class Cuj {
CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
CUJ_FOLD_ANIM,
CUJ_DESKTOP_MODE_RESIZE_WINDOW,
- CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG,
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU,
CUJ_DESKTOP_MODE_EXIT_MODE,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
@@ -305,7 +318,8 @@ public class Cuj {
CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
- CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -414,7 +428,7 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
@@ -423,6 +437,7 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
}
private Cuj() {
@@ -631,8 +646,8 @@ public class Cuj {
return "FOLD_ANIM";
case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
return "DESKTOP_MODE_RESIZE_WINDOW";
- case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG:
- return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG";
+ case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD:
+ return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD";
case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU:
return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU";
case CUJ_DESKTOP_MODE_EXIT_MODE:
@@ -649,6 +664,8 @@ public class Cuj {
return "LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
+ case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE:
+ return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 24971f51aabf..488e06f9a1ad 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -580,10 +580,15 @@ public final class PowerStats {
}
PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
+ String formattedStats = uidStatsFormatter.format(uidStats.valueAt(i));
+ if (formattedStats.isBlank()) {
+ continue;
+ }
+
pw.print("UID ");
pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
pw.print(": ");
- pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
+ pw.print(formattedStats);
pw.println();
}
pw.decreaseIndent();
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index d552e0b8c643..ae43acfdedb6 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -18,7 +18,7 @@
"Kernel[^/]*\\.java",
"[^/]*Power[^/]*\\.java"
],
- "name": "BatteryUsageStatsProtoTests"
+ "name": "PowerStatsTests"
},
{
"file_patterns": [
diff --git a/core/java/com/android/internal/policy/FoldLockSettingsObserver.java b/core/java/com/android/internal/policy/FoldLockSettingsObserver.java
new file mode 100644
index 000000000000..c6fba8a31277
--- /dev/null
+++ b/core/java/com/android/internal/policy/FoldLockSettingsObserver.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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.internal.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Set;
+
+/**
+ * A ContentObserver that listens for changes in the "Continue using apps on fold" setting. This
+ * setting determines a device's behavior when the user folds the device.
+ * @hide
+ *
+ * Keep the setting values in this class in sync with the values in
+ * {@link com.android.server.utils.FoldSettingProvider} and
+ * {@link com.android.settings.display.FoldLockBehaviorSettings}
+ */
+public class FoldLockSettingsObserver extends ContentObserver {
+ /** The setting for "stay awake on fold". */
+ public static final String SETTING_VALUE_STAY_AWAKE_ON_FOLD = "stay_awake_on_fold_key";
+ /** The setting for "swipe up to continue". */
+ public static final String SETTING_VALUE_SELECTIVE_STAY_AWAKE = "selective_stay_awake_key";
+ /** The setting for "always sleep on fold". */
+ public static final String SETTING_VALUE_SLEEP_ON_FOLD = "sleep_on_fold_key";
+ public static final String SETTING_VALUE_DEFAULT = SETTING_VALUE_SELECTIVE_STAY_AWAKE;
+ private static final Set<String> SETTING_VALUES = Set.of(SETTING_VALUE_STAY_AWAKE_ON_FOLD,
+ SETTING_VALUE_SELECTIVE_STAY_AWAKE, SETTING_VALUE_SLEEP_ON_FOLD);
+
+ private final Context mContext;
+
+ /** The cached value of the setting. */
+ @VisibleForTesting
+ String mFoldLockSetting;
+
+ public FoldLockSettingsObserver(Handler handler, Context context) {
+ super(handler);
+ mContext = context;
+ }
+
+ /** Registers the observer and updates the cache for the first time. */
+ public void register() {
+ final ContentResolver r = mContext.getContentResolver();
+ r.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.FOLD_LOCK_BEHAVIOR),
+ false, this, UserHandle.USER_ALL);
+ requestAndCacheFoldLockSetting();
+ }
+
+ /** Unregisters the observer. */
+ public void unregister() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ /** Runs when settings changes. */
+ @Override
+ public void onChange(boolean selfChange) {
+ requestAndCacheFoldLockSetting();
+ }
+
+ /**
+ * Requests and caches the current FOLD_LOCK_BEHAVIOR setting, which should be one of three
+ * values: SETTING_VALUE_STAY_AWAKE_ON_FOLD, SETTING_VALUE_SELECTIVE_STAY_AWAKE,
+ * SETTING_VALUE_SLEEP_ON_FOLD. If null (not set), returns the system default.
+ */
+ @VisibleForTesting
+ void requestAndCacheFoldLockSetting() {
+ String currentSetting = request();
+
+ if (currentSetting == null || !SETTING_VALUES.contains(currentSetting)) {
+ currentSetting = SETTING_VALUE_DEFAULT;
+ }
+
+ setCurrentFoldSetting(currentSetting);
+ }
+
+ /**
+ * Makes a binder call to request the current FOLD_LOCK_BEHAVIOR setting.
+ */
+ @VisibleForTesting
+ @Nullable
+ String request() {
+ return Settings.System.getStringForUser(mContext.getContentResolver(),
+ Settings.System.FOLD_LOCK_BEHAVIOR, UserHandle.USER_CURRENT);
+ }
+
+ /** Caches the fold-lock behavior received from Settings. */
+ @VisibleForTesting
+ void setCurrentFoldSetting(String newSetting) {
+ mFoldLockSetting = newSetting;
+ }
+
+ /** Used by external requesters: checks if the current setting is "stay awake on fold". */
+ public boolean isStayAwakeOnFold() {
+ return mFoldLockSetting.equals(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
+ }
+
+ /** Used by external requesters: checks if the current setting is "swipe up to continue". */
+ public boolean isSelectiveStayAwake() {
+ return mFoldLockSetting.equals(SETTING_VALUE_SELECTIVE_STAY_AWAKE);
+ }
+
+ /** Used by external requesters: checks if the current setting is "sleep on fold". */
+ public boolean isSleepOnFold() {
+ return mFoldLockSetting.equals(SETTING_VALUE_SLEEP_ON_FOLD);
+ }
+}
diff --git a/core/java/com/android/internal/protolog/IProtoLogClient.aidl b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
new file mode 100644
index 000000000000..969ed99d3aca
--- /dev/null
+++ b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog;
+
+/**
+ * The ProtoLog client interface.
+ *
+ * These clients will communicate bi-directionally with the ProtoLog service
+ * (@see IProtoLogService.aidl) running in the system process.
+ *
+ * {@hide}
+ */
+interface IProtoLogClient {
+ void toggleLogcat(boolean enabled, in String[] groups);
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/IProtoLogService.aidl b/core/java/com/android/internal/protolog/IProtoLogService.aidl
new file mode 100644
index 000000000000..cc349ea2985a
--- /dev/null
+++ b/core/java/com/android/internal/protolog/IProtoLogService.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog;
+
+import com.android.internal.protolog.IProtoLogClient;
+
+/**
+ * The ProtoLog Service interface.
+ *
+ * This service runs in system server.
+ *
+ * All ProtoLog clients (in theory one per process) will register themselves to this service.
+ * This service will then serve as the entry point for any shell command based interactions with
+ * protolog and get/forward any required information/actions from/to all the registered ProtoLog
+ * clients.
+ *
+ * Clients will be responsible for directly sending all their trace data to Perfetto without passing
+ * through this service, except viewer config data, the mappings from messages hashes (generated by
+ * the ProtoLog Tool if and when the tool processed the source code). So, this service is
+ * responsible for dumping the viewer config data from all clients. The reason for this is because
+ * we do this on Perfetto's onFlush callback when a tracing instance is stopped. However, if a
+ * client process is frozen then this will not dump; system server, where this service runs cannot
+ * be frozen. Secondly many processes (i.e. apps) will run the same code with the same viewer config
+ * data, so this service allows us to orchestrate which config gets dumped so we don't duplicate
+ * this information in the trace and waste valuable trace space.
+ *
+ * {@hide}
+ */
+interface IProtoLogService {
+ interface IRegisterClientArgs {
+ String[] getGroups();
+ boolean[] getGroupsDefaultLogcatStatus();
+ String getViewerConfigFile();
+ }
+
+ void registerClient(IProtoLogClient client, IRegisterClientArgs args);
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 652cba7ed00d..fbec1f104fc8 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -88,7 +88,7 @@ import java.util.concurrent.locks.ReentrantLock;
/**
* A service for the ProtoLog logging system.
*/
-public class PerfettoProtoLogImpl implements IProtoLog {
+public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
private static final String LOG_TAG = "ProtoLog";
public static final String NULL_STRING = "null";
private final AtomicInteger mTracingInstances = new AtomicInteger();
@@ -100,6 +100,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
);
@Nullable
private final ProtoLogViewerConfigReader mViewerConfigReader;
+ @Nullable
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private final Runnable mCacheUpdater;
@@ -111,13 +112,12 @@ public class PerfettoProtoLogImpl implements IProtoLog {
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(String viewerConfigFilePath, Runnable cacheUpdater) {
+ public PerfettoProtoLogImpl(@NonNull String viewerConfigFilePath, Runnable cacheUpdater) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
} catch (FileNotFoundException e) {
- Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
- return null;
+ throw new RuntimeException("Failed to load viewer config file " + viewerConfigFilePath, e);
}
}, cacheUpdater);
}
@@ -127,7 +127,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
}
public PerfettoProtoLogImpl(
- @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
@@ -203,13 +203,23 @@ public class PerfettoProtoLogImpl implements IProtoLog {
return mTracingInstances.get() > 0;
}
+ @Override
+ public void toggleLogcat(boolean enabled, String[] groups) {
+ final ILogger logger = (message) -> Log.d(LOG_TAG, message);
+ if (enabled) {
+ startLoggingToLogcat(groups, logger);
+ } else {
+ stopLoggingToLogcat(groups, logger);
+ }
+ }
+
/**
* Start text logging
* @param groups Groups to start text logging for
* @param logger A logger to write status updates to
* @return status code
*/
- public int startLoggingToLogcat(String[] groups, ILogger logger) {
+ public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
if (mViewerConfigReader != null) {
mViewerConfigReader.loadViewerConfig(groups, logger);
}
@@ -222,7 +232,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
* @param logger A logger to write status updates to
* @return status code
*/
- public int stopLoggingToLogcat(String[] groups, ILogger logger) {
+ public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
if (mViewerConfigReader != null) {
mViewerConfigReader.unloadViewerConfig(groups, logger);
}
@@ -242,13 +252,29 @@ public class PerfettoProtoLogImpl implements IProtoLog {
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
mLogGroups.put(protoLogGroup.name(), protoLogGroup);
}
+
+ final String[] groupsLoggingToLogcat = Arrays.stream(protoLogGroups)
+ .filter(IProtoLogGroup::isLogToLogcat)
+ .map(IProtoLogGroup::name)
+ .toArray(String[]::new);
+
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat);
+ }
}
/**
* Responds to a shell command.
*/
+ @Deprecated
public int onShellCommand(ShellCommand shell) {
PrintWriter pw = shell.getOutPrintWriter();
+
+ if (android.tracing.Flags.clientSideProtoLogging()) {
+ pw.println("Command deprecated. Please use 'cmd protolog' instead.");
+ return -1;
+ }
+
String cmd = shell.getNextArg();
if (cmd == null) {
return unknownCommand(pw);
@@ -742,7 +768,8 @@ public class PerfettoProtoLogImpl implements IProtoLog {
return -1;
}
- private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
+ private synchronized void onTracingInstanceStart(
+ int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
mDefaultLogLevelCounts[i]++;
@@ -775,7 +802,8 @@ public class PerfettoProtoLogImpl implements IProtoLog {
this.mTracingInstances.incrementAndGet();
}
- private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
+ private synchronized void onTracingInstanceStop(
+ int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
this.mTracingInstances.decrementAndGet();
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 84f3237142b8..6dc6585b5caf 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -49,12 +49,12 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
ProtoLogDataSource.TlsState,
ProtoLogDataSource.IncrementalState> {
- private final Consumer<ProtoLogConfig> mOnStart;
+ private final Instance.TracingInstanceStartCallback mOnStart;
private final Runnable mOnFlush;
- private final Consumer<ProtoLogConfig> mOnStop;
+ private final Instance.TracingInstanceStopCallback mOnStop;
- public ProtoLogDataSource(Consumer<ProtoLogConfig> onStart, Runnable onFlush,
- Consumer<ProtoLogConfig> onStop) {
+ public ProtoLogDataSource(Instance.TracingInstanceStartCallback onStart, Runnable onFlush,
+ Instance.TracingInstanceStopCallback onStop) {
super("android.protolog");
this.mOnStart = onStart;
this.mOnFlush = onFlush;
@@ -267,20 +267,30 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
public static class Instance extends DataSourceInstance {
- private final Consumer<ProtoLogConfig> mOnStart;
+ public interface TracingInstanceStartCallback {
+ void run(int instanceIdx, ProtoLogConfig config);
+ }
+
+ public interface TracingInstanceStopCallback {
+ void run(int instanceIdx, ProtoLogConfig config);
+ }
+
+ private final TracingInstanceStartCallback mOnStart;
private final Runnable mOnFlush;
- private final Consumer<ProtoLogConfig> mOnStop;
+ private final TracingInstanceStopCallback mOnStop;
private final ProtoLogConfig mConfig;
+ private final int mInstanceIndex;
public Instance(
DataSource<Instance, TlsState, IncrementalState> dataSource,
int instanceIdx,
ProtoLogConfig config,
- Consumer<ProtoLogConfig> onStart,
+ TracingInstanceStartCallback onStart,
Runnable onFlush,
- Consumer<ProtoLogConfig> onStop
+ TracingInstanceStopCallback onStop
) {
super(dataSource, instanceIdx);
+ this.mInstanceIndex = instanceIdx;
this.mOnStart = onStart;
this.mOnFlush = onFlush;
this.mOnStop = onStop;
@@ -289,7 +299,7 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
@Override
public void onStart(StartCallbackArguments args) {
- this.mOnStart.accept(this.mConfig);
+ this.mOnStart.run(this.mInstanceIndex, this.mConfig);
}
@Override
@@ -299,7 +309,7 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
@Override
public void onStop(StopCallbackArguments args) {
- this.mOnStop.accept(this.mConfig);
+ this.mOnStop.run(this.mInstanceIndex, this.mConfig);
}
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 3082295a522c..77ca7ce91b22 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -30,6 +30,7 @@ import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLogToolInjected;
+import java.io.File;
import java.util.TreeMap;
/**
@@ -105,7 +106,15 @@ public class ProtoLogImpl {
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+ File f = new File(sViewerConfigPath);
+ if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+ // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+ // In so tests the viewer config file might not exist in which we don't
+ // want to provide config path to the user
+ sServiceInstance = new PerfettoProtoLogImpl(null, null, sCacheUpdater);
+ } else {
+ sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+ }
} else {
sServiceInstance = new LegacyProtoLogImpl(
sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index bb6c8b7a9698..38ca0d8f75e8 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -24,12 +24,13 @@ import java.util.Set;
import java.util.TreeMap;
public class ProtoLogViewerConfigReader {
+ @NonNull
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
private final Map<String, Set<Long>> mGroupHashes = new TreeMap<>();
private final LongSparseArray<String> mLogMessageMap = new LongSparseArray<>();
public ProtoLogViewerConfigReader(
- ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+ @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
}
diff --git a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
index 334f5488425a..14bc8e4782f2 100644
--- a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
+++ b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
@@ -16,11 +16,13 @@
package com.android.internal.protolog;
+import android.annotation.NonNull;
import android.util.proto.ProtoInputStream;
public interface ViewerConfigInputStreamProvider {
/**
* @return a ProtoInputStream.
*/
+ @NonNull
ProtoInputStream getInputStream();
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index c07fd3838837..7c62615cdc42 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,6 +27,7 @@
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
+#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -41,8 +42,10 @@
#include <system/audio_policy.h>
#include <utils/Log.h>
+#include <thread>
#include <optional>
#include <sstream>
+#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -261,6 +264,13 @@ static struct {
jfieldID mMixerBehavior;
} gAudioMixerAttributesField;
+static struct {
+ jclass clazz;
+ jmethodID run;
+} gRunnableClassInfo;
+
+static JavaVM* gVm;
+
static Mutex gLock;
enum AudioError {
@@ -3362,6 +3372,55 @@ static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIE
return enabled;
}
+class JavaSystemPropertyListener {
+ public:
+ JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
+ mCallback(env->NewGlobalRef(javaCallback)),
+ mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
+ mListenerThread = std::thread([this]() mutable {
+ JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
+ while (!mCleanupSignal.load()) {
+ using namespace std::chrono_literals;
+ // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
+ // be destroyed.
+ std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
+ if (newVal != "" && mLastVal != newVal) {
+ threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
+ mLastVal = std::move(newVal);
+ }
+ }
+ });
+ }
+
+ ~JavaSystemPropertyListener() {
+ mCleanupSignal.store(true);
+ mListenerThread.join();
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
+ env->DeleteGlobalRef(mCallback);
+ }
+
+ private:
+ jobject mCallback;
+ android::base::CachedProperty mCachedProperty;
+ std::thread mListenerThread;
+ std::atomic<bool> mCleanupSignal{false};
+ std::string mLastVal = "";
+};
+
+std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
+std::mutex gSysPropLock{};
+
+static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
+ jstring sysProp,
+ jobject javaCallback) {
+ ScopedUtfChars sysPropChars{env, sysProp};
+ auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
+ std::string{sysPropChars.c_str()});
+ std::unique_lock _l{gSysPropLock};
+ gSystemPropertyListeners.push_back(std::move(listener));
+}
+
+
// ----------------------------------------------------------------------------
#define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3534,7 +3593,12 @@ static const JNINativeMethod gMethods[] =
android_media_AudioSystem_clearPreferredMixerAttributes),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
- MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
+ MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
+ MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
+ "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+ android_media_AudioSystem_listenForSystemPropertyChange),
+
+ };
static const JNINativeMethod gEventHandlerMethods[] =
{MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3816,6 +3880,12 @@ int register_android_media_AudioSystem(JNIEnv *env)
gAudioMixerAttributesField.mMixerBehavior =
GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
+ jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
+ gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
+ gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
+
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
+
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index d32486c73db2..240be3fe5534 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -415,7 +415,7 @@ static void android_view_MotionEvent_nativeAddBatch(JNIEnv* env, jclass clazz,
env->DeleteLocalRef(pointerCoordsObj);
}
- event->addSample(eventTimeNanos, rawPointerCoords.data());
+ event->addSample(eventTimeNanos, rawPointerCoords.data(), event->getId());
event->setMetaState(event->getMetaState() | metaState);
}
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 7e2a5ace7e64..020b27e82bea 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -47,13 +47,12 @@ using namespace std;
*/
extern int register_android_os_Binder(JNIEnv* env);
-extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env);
+extern int register_libcore_util_NativeAllocationRegistry(JNIEnv* env);
typedef void (*FreeFunction)(void*);
-static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, jclass,
- jlong freeFunction,
- jlong ptr) {
+static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*, jclass, jlong freeFunction,
+ jlong ptr) {
void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
FreeFunction nativeFreeFunction =
reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
@@ -61,11 +60,11 @@ static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, j
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(NativeAllocationRegistry_Delegate, nativeApplyFreeFunction, "(JJ)V"),
+ NATIVE_METHOD(NativeAllocationRegistry, applyFreeFunction, "(JJ)V"),
};
-int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry_Delegate", gMethods,
+int register_libcore_util_NativeAllocationRegistry(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry", gMethods,
NELEM(gMethods));
}
@@ -147,8 +146,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
- {"libcore.util.NativeAllocationRegistry_Delegate",
- REG_JNI(register_libcore_util_NativeAllocationRegistry_Delegate)},
+ {"libcore.util.NativeAllocationRegistry",
+ REG_JNI(register_libcore_util_NativeAllocationRegistry)},
};
static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 5a4d6dbdc085..12804d43b04e 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -147,6 +147,7 @@ message VibrationProto {
IGNORED_ON_WIRELESS_CHARGER = 27;
IGNORED_MISSING_PERMISSION = 28;
CANCELLED_BY_APP_OPS = 29;
+ CANCELLED_BY_FOREGROUND_USER = 30;
reserved 17; // prev IGNORED_UNKNOWN_VIBRATION
}
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dc3d9355f148..236e7c5b7a62 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7090,4 +7090,7 @@
<!-- Whether to show GAIA education screen during account login of private space setup.
OEM/Partner can explicitly opt to disable the screen. -->
<bool name="config_enableGaiaEducationInPrivateSpace">true</bool>
+
+ <!-- Whether to enable usb state update via udc sysfs. -->
+ <bool name="config_enableUdcSysfsUsbStateUpdate">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fcafdaed8d1a..09688f2f7bec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -523,6 +523,7 @@
<java-symbol type="integer" name="config_defaultAnalogClockSecondsHandFps"/>
<java-symbol type="bool" name="config_notificationCloseButtonSupported"/>
<java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/>
+ <java-symbol type="bool" name="config_enableUdcSysfsUsbStateUpdate"/>
<java-symbol type="color" name="tab_indicator_text_v4" />
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
index 9d7d71d8d539..616c72ec6078 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
@@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals;
import android.graphics.Matrix;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
index d7b911dda672..d0a4141eb9ed 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
@@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull;
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiTest;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 4839dd27b283..013117e89c3e 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
@@ -45,8 +45,8 @@ import android.util.StringBuilderPrinter;
import android.view.MotionEvent;
import android.view.autofill.AutofillId;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index ce85a76f478d..61bf1378fc36 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -28,9 +28,9 @@ import android.os.Bundle;
import android.os.Parcel;
import android.platform.test.flag.junit.SetFlagsRule;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.frameworks.inputmethodcoretests.R;
@@ -135,7 +135,7 @@ public class InputMethodInfoTest {
private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
throws Exception {
- final Context context = InstrumentationRegistry.getContext();
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
final ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.applicationInfo = context.getApplicationInfo();
serviceInfo.packageName = context.getPackageName();
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
index d70572444128..812b3f5cd153 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
@@ -24,9 +24,9 @@ import static org.junit.Assert.assertNotNull;
import android.content.Context;
import android.hardware.display.DisplayManager;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
index e7b1110f898a..73ff30408a7b 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
@@ -26,8 +26,8 @@ import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
index 5095cad1b607..4c76992cbcac 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
@@ -27,8 +27,8 @@ import android.annotation.Nullable;
import android.os.Parcel;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
index 47a724d36038..608dd4d9fca1 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
@@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull;
import android.graphics.PointF;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiTest;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
index a94f8772fcd0..bb6a944ba17f 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
@@ -24,8 +24,8 @@ import android.os.CancellationSignal;
import android.os.CancellationSignalBeamer;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiTest;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
index b2eb07c0a9e7..4cbd7ab1ef65 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
@@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull;
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiTest;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
index df63a4aaaefe..c1e2197b8d85 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
@@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull;
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiTest;
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
index f264cc630dc5..724d729c950f 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
@@ -25,8 +25,8 @@ import android.graphics.RectF;
import android.os.Parcel;
import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
index a6262944e8b0..9eb5dd1a2422 100644
--- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
@@ -22,8 +22,8 @@ import android.platform.test.annotations.Presubmit;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
index 32bfdcb7b217..6ac36398ad55 100644
--- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
@@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals;
import android.view.WindowManager.LayoutParams;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
index ba6390808151..7b0a5dacbea0 100644
--- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
@@ -18,8 +18,8 @@ package com.android.internal.inputmethod;
import static org.junit.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp
deleted file mode 100644
index 1fb5f2c0789b..000000000000
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "BatteryUsageStatsProtoTests",
- srcs: ["src/**/*.java"],
-
- static_libs: [
- "androidx.test.rules",
- "junit",
- "mockito-target-minus-junit4",
- "platform-test-annotations",
- "platformprotosnano",
- "statsdprotolite",
- "truth",
- ],
-
- libs: ["android.test.runner"],
-
- platform_apis: true,
- certificate: "platform",
-
- test_suites: ["device-tests"],
-}
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index cb3f99c37a4f..33a46d0fde17 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -35,6 +35,8 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
@MediumTest
public class AnimatorSetCallsTest {
@@ -447,6 +449,43 @@ public class AnimatorSetCallsTest {
mActivity.runOnUiThread(() -> {});
}
+ @Test
+ public void startAfterSeek() throws Throwable {
+ ArrayList<Float> values = new ArrayList<>();
+ AtomicReference<CountDownLatch> drawLatch = new AtomicReference<>(new CountDownLatch(1));
+
+ mActivity.runOnUiThread(() -> {
+ mAnimator.setDuration(300);
+ mAnimator.setInterpolator(null);
+ View view = (View) mAnimator.getTarget();
+ view.getViewTreeObserver().addOnDrawListener(() -> {
+ values.add(view.getTranslationX());
+ drawLatch.get().countDown();
+ });
+ mSet1.setCurrentPlayTime(150);
+ });
+
+ assertTrue(drawLatch.get().await(1, TimeUnit.SECONDS));
+ drawLatch.set(new CountDownLatch(1));
+
+ mActivity.runOnUiThread(() -> {
+ assertEquals(1, values.size());
+ assertEquals(50f, values.get(0), 0.01f);
+ mSet1.start();
+ });
+
+ assertTrue(drawLatch.get().await(1, TimeUnit.SECONDS));
+
+ mActivity.runOnUiThread(() -> {
+ assertTrue(values.size() >= 2);
+ float lastValue = values.get(0);
+ for (int i = 1; i < values.size(); i++) {
+ assertTrue(values.get(i) >= lastValue);
+ lastValue = values.get(i);
+ }
+ });
+ }
+
private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
final boolean[] value = new boolean[1];
PollingCheck.waitFor(() -> {
diff --git a/core/tests/coretests/src/android/animation/OWNERS b/core/tests/coretests/src/android/animation/OWNERS
new file mode 100644
index 000000000000..1eefb3a3dc65
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/animation/OWNERS
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index e8a0762f70c0..294352e7f928 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -90,7 +90,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
@@ -117,6 +118,9 @@ public class ActivityThreadTest {
// few sequence numbers the framework used to launch the test activity.
private static final int BASE_SEQ = 10000000;
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+
@Rule(order = 0)
public final ActivityTestRule<TestActivity> mActivityTestRule =
new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
@@ -133,8 +137,6 @@ public class ActivityThreadTest {
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
-
// Keep track of the original controller, so that it can be used to restore in tearDown()
// when there is override in some test cases.
mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index c7060adc1ca1..72c4639277bb 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -47,10 +47,12 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/**
* Tests for subtypes of {@link ClientTransactionItem}.
@@ -63,6 +65,9 @@ import org.mockito.MockitoAnnotations;
@Presubmit
public class ClientTransactionItemTest {
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+
@Mock
private ClientTransactionHandler mHandler;
@Mock
@@ -89,7 +94,6 @@ public class ClientTransactionItemTest {
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
mGlobalConfig = new Configuration();
mConfiguration = new Configuration();
mActivitiesToBeDestroyed = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index d2a444f0d0df..f9609fcad8a1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -59,7 +59,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiConsumer;
@@ -76,6 +77,8 @@ import java.util.function.BiConsumer;
public class ClientTransactionListenerControllerTest {
@Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+ @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Mock
@@ -100,7 +103,6 @@ public class ClientTransactionListenerControllerTest {
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
mHandler = getInstrumentation().getContext().getMainThreadHandler();
mController = spy(ClientTransactionListenerController
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 32e611cf0a1e..e429cfc687bb 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -21,12 +21,7 @@ import static android.app.servertransaction.TestUtils.mergedConfig;
import static android.app.servertransaction.TestUtils.referrerIntentList;
import static android.app.servertransaction.TestUtils.resultInfoList;
-import static com.android.window.flags.Flags.FLAG_DISABLE_OBJECT_POOL;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
import android.annotation.NonNull;
import android.app.ActivityOptions;
@@ -41,27 +36,20 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.window.ActivityWindowInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.window.flags.Flags;
-
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
-import java.util.List;
import java.util.function.Supplier;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
/**
* Tests for {@link ObjectPool}.
*
@@ -71,33 +59,19 @@ import platform.test.runner.parameterized.Parameters;
* <p>This test class is a part of Window Manager Service tests and specified in
* {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
*/
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class ObjectPoolTests {
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(FLAG_DISABLE_OBJECT_POOL);
- }
-
@Rule
- public SetFlagsRule mSetFlagsRule;
+ public final MockitoRule mocks = MockitoJUnit.rule();
@Mock
private IApplicationThread mApplicationThread;
@Mock
private IBinder mActivityToken;
- public ObjectPoolTests(FlagsParameterization flags) {
- mSetFlagsRule = new SetFlagsRule(flags);
- }
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
// 1. Check if two obtained objects from pool are not the same.
// 2. Check if the state of the object is cleared after recycling.
// 3. Check if the same object is obtained from pool after recycling.
@@ -221,30 +195,11 @@ public class ObjectPoolTests {
item.recycle();
final ObjectPoolItem item2 = obtain.get();
- if (Flags.disableObjectPool()) {
- assertNotSame(item, item2); // Different instance.
- } else {
- assertSame(item, item2);
- }
+ assertNotSame(item, item2); // Different instance.
// Create new object when the pool is empty.
final ObjectPoolItem item3 = obtain.get();
assertNotSame(item, item3);
- if (Flags.disableObjectPool()) {
- // Skip recycle if flag enabled, compare unnecessary.
- return;
- }
- assertEquals(item, item3);
-
- // Reset fields after recycle.
- item.recycle();
-
- assertNotEquals(item, item3);
-
- // Recycled objects are equal.
- item3.recycle();
-
- assertEquals(item, item3);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 73b7447a31c0..eb69b9c8b6e1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -57,11 +57,13 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.Arrays;
import java.util.Collections;
@@ -83,6 +85,9 @@ import java.util.stream.Collectors;
@Presubmit
public class TransactionExecutorTests {
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+
@Mock
private ClientTransactionHandler mTransactionHandler;
@Mock
@@ -98,8 +103,6 @@ public class TransactionExecutorTests {
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
mClientRecord = new ActivityClientRecord();
when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord);
diff --git a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
index 6998c32978c1..f5e9cc6953fb 100644
--- a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
+++ b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
@@ -26,8 +26,8 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 71c068d7ad46..9750de386271 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -35,6 +35,7 @@ import static android.text.format.DateUtils.FORMAT_SHOW_YEAR;
import static android.text.format.DateUtils.FORMAT_UTC;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.icu.util.Calendar;
import android.icu.util.TimeZone;
@@ -683,4 +684,12 @@ public class DateIntervalFormatTest {
assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
fmt.apply(1077840000000L, 1078185600000L));
}
+
+ @Test
+ public void testIsLibcoreVFlagEnabled() {
+ // This flag has been fully ramped. It should never be false.
+ assertTrue(DateIntervalFormat.isLibcoreVFlagEnabled());
+ // Call a Android V API in libcore.
+ assertEquals("\ud840\udc2b", Character.toString(0x2002B));
+ }
}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 47b288993d11..248db65d7435 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -37,8 +37,12 @@ import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
@@ -46,6 +50,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -61,6 +66,9 @@ import org.mockito.Spy;
@RunWith(AndroidJUnit4.class)
public class ImeInsetsSourceConsumerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
InsetsSourceConsumer mImeConsumer;
@Spy InsetsController mController;
@@ -112,6 +120,7 @@ public class ImeInsetsSourceConsumerTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testImeRequestedVisibleAwaitingControl() {
// Set null control and then request show.
mController.onControlsChanged(new InsetsSourceControl[] { null });
@@ -141,6 +150,7 @@ public class ImeInsetsSourceConsumerTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testImeRequestedVisibleAwaitingLeash() {
// Set null control, then request show.
mController.onControlsChanged(new InsetsSourceControl[] { null });
@@ -185,6 +195,7 @@ public class ImeInsetsSourceConsumerTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testImeGetAndClearSkipAnimationOnce_expectSkip() {
// Expect IME animation will skipped when the IME is visible at first place.
verifyImeGetAndClearSkipAnimationOnce(true /* hasWindowFocus */, true /* hasViewFocus */,
@@ -192,6 +203,7 @@ public class ImeInsetsSourceConsumerTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testImeGetAndClearSkipAnimationOnce_expectNoSkip() {
// Expect IME animation will not skipped if previously no view focused when gained the
// window focus and requesting the IME visible next time.
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index b68ff78107ca..62291d405a60 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -237,8 +237,8 @@ public class ViewFrameRateTest {
return;
}
waitForFrameRateCategoryToSettle();
- assertEquals(FRAME_RATE_CATEGORY_LOW,
- mViewRoot.getLastPreferredFrameRateCategory());
+ assertTrue(mViewRoot.getLastPreferredFrameRateCategory()
+ < FRAME_RATE_CATEGORY_HIGH_HINT);
int width = mMovingView.getWidth();
int height = mMovingView.getHeight();
diff --git a/core/tests/coretests/src/android/view/WindowInfoTest.java b/core/tests/coretests/src/android/view/WindowInfoTest.java
index d927f0632273..43e678fb634c 100644
--- a/core/tests/coretests/src/android/view/WindowInfoTest.java
+++ b/core/tests/coretests/src/android/view/WindowInfoTest.java
@@ -34,8 +34,8 @@ import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index ab4543cb8001..ba1204b9cc23 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals;
@@ -26,12 +27,14 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
@@ -68,4 +71,17 @@ public class WindowInsetsTest {
true /* compatIgnoreVisibility */, null, null, 0, 0);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
+
+ @Test
+ public void testSetBoundingRectsInBuilder_noInsets_preservedInWindowInsets() {
+ final List<Rect> rects = List.of(new Rect(0, 0, 50, 100));
+ final WindowInsets insets =
+ new WindowInsets.Builder()
+ .setBoundingRects(captionBar(), rects)
+ .setBoundingRectsIgnoringVisibility(captionBar(), rects)
+ .build();
+
+ assertEquals(rects, insets.getBoundingRects(captionBar()));
+ assertEquals(rects, insets.getBoundingRectsIgnoringVisibility(captionBar()));
+ }
}
diff --git a/core/tests/coretests/src/android/view/WindowManagerTests.java b/core/tests/coretests/src/android/view/WindowManagerTests.java
index c5a9d480771e..211d768dde93 100644
--- a/core/tests/coretests/src/android/view/WindowManagerTests.java
+++ b/core/tests/coretests/src/android/view/WindowManagerTests.java
@@ -25,8 +25,8 @@ import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index 39ea8af6fed9..f3ddfa68489f 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -27,9 +27,9 @@ import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3d4918b1bd42..2d82d231e279 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@ public class AccessibilityNodeInfoTest {
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 43;
+ private static final int NUM_MARSHALLED_PROPERTIES = 44;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 3147eac30705..8db13c845b38 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -30,8 +30,8 @@ import static android.window.SystemPerformanceHinter.HINT_SF_FRAME_RATE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -42,14 +42,16 @@ import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.HashMap;
@@ -63,6 +65,9 @@ import java.util.HashMap;
@RunWith(AndroidJUnit4.class)
public class SystemPerformanceHinterTests {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
private static final int DEFAULT_DISPLAY_ID = android.view.Display.DEFAULT_DISPLAY;
private static final int SECONDARY_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1;
private static final int NO_ROOT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2;
@@ -83,8 +88,6 @@ public class SystemPerformanceHinterTests {
@Before
public void setUpOnce() {
- MockitoAnnotations.initMocks(this);
-
mDefaultDisplayRoot = new SurfaceControl();
mSecondaryDisplayRoot = new SurfaceControl();
mRootProvider = new SystemPerformanceHinterTests.RootProvider();
diff --git a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
index 2dadb20d8f2c..4589607adb82 100644
--- a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
+++ b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
@@ -24,9 +24,9 @@ import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 30c0f2b682db..1f60b3121192 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -34,14 +34,16 @@ import static org.mockito.Mockito.verify;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/**
* Tests for {@link WindowContextController}
@@ -56,6 +58,10 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@Presubmit
public class WindowContextControllerTest {
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
private WindowContextController mController;
@Mock
private WindowTokenClientController mWindowTokenClientController;
@@ -64,7 +70,6 @@ public class WindowContextControllerTest {
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
mController = spy(new WindowContextController(mMockToken));
doReturn(mWindowTokenClientController).when(mController).getWindowTokenClientController();
doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index b2a404449de7..f1fbd559b8f8 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -53,10 +53,10 @@ import android.view.WindowManagerGlobal;
import android.view.WindowManagerImpl;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
index 7cbb6b44ea57..accc0206e6e0 100644
--- a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
+++ b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
@@ -31,9 +31,9 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.window.flags.Flags;
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 9ae96a0fc9d8..d153edd9da39 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -44,18 +44,20 @@ import android.view.IWindowSession;
import android.view.ImeBackAnimationController;
import android.view.MotionEvent;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
@@ -70,6 +72,10 @@ import java.util.List;
@SmallTest
@Presubmit
public class WindowOnBackInvokedDispatcherTest {
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
@Mock
private IWindowSession mWindowSession;
@Mock
@@ -106,8 +112,6 @@ public class WindowOnBackInvokedDispatcherTest {
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
doReturn(true).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index a21c91782214..a3725af04ba6 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -39,9 +39,11 @@ import android.view.IWindowManager;
import androidx.test.filters.SmallTest;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/**
* Tests for {@link WindowTokenClientController}.
@@ -53,6 +55,9 @@ import org.mockito.MockitoAnnotations;
@Presubmit
public class WindowTokenClientControllerTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
@Mock
private IWindowManager mWindowManagerService;
@Mock
@@ -67,7 +72,6 @@ public class WindowTokenClientControllerTest {
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
mController = spy(WindowTokenClientController.createInstanceForTesting());
doReturn(mWindowManagerService).when(mController).getWindowManagerService();
mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY);
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index 9292f667b27c..aa4c28a55865 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -20,8 +20,8 @@ import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java b/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java
new file mode 100644
index 000000000000..537dd69d017d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.internal.policy;
+
+import static com.android.internal.policy.FoldLockSettingsObserver.SETTING_VALUE_DEFAULT;
+import static com.android.internal.policy.FoldLockSettingsObserver.SETTING_VALUE_SLEEP_ON_FOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link FoldLockSettingsObserver}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FoldLockSettingsObserverTest {
+ @Mock
+ private Context mContext;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private ContentResolver mContentResolver;
+
+ private FoldLockSettingsObserver mFoldLockSettingsObserver;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFoldLockSettingsObserver =
+ spy(new FoldLockSettingsObserver(mHandler, mContext));
+
+ doReturn(mContentResolver).when(mContext).getContentResolver();
+ doReturn(SETTING_VALUE_DEFAULT).when(mFoldLockSettingsObserver).request();
+
+ mFoldLockSettingsObserver.register();
+ }
+
+ @Test
+ public void shouldRegister() {
+ doReturn(mContentResolver).when(mContext).getContentResolver();
+
+ mFoldLockSettingsObserver.register();
+
+ verify(mContentResolver).registerContentObserver(
+ Settings.System.getUriFor(Settings.System.FOLD_LOCK_BEHAVIOR),
+ false /*notifyForDescendants */,
+ mFoldLockSettingsObserver,
+ UserHandle.USER_ALL
+ );
+ }
+
+ @Test
+ public void shouldUnregister() {
+ mFoldLockSettingsObserver.unregister();
+
+ verify(mContentResolver).unregisterContentObserver(mFoldLockSettingsObserver);
+ }
+
+ @Test
+ public void shouldCacheNewValue() {
+ // Reset the mock's behavior and call count to zero.
+ reset(mFoldLockSettingsObserver);
+ doReturn(SETTING_VALUE_SLEEP_ON_FOLD).when(mFoldLockSettingsObserver).request();
+
+ // Setting is DEFAULT at first.
+ assertEquals(SETTING_VALUE_DEFAULT, mFoldLockSettingsObserver.mFoldLockSetting);
+
+ // Cache new setting.
+ mFoldLockSettingsObserver.requestAndCacheFoldLockSetting();
+
+ // Check that setter was called once and change went through properly.
+ verify(mFoldLockSettingsObserver).setCurrentFoldSetting(anyString());
+ assertTrue(mFoldLockSettingsObserver.isSleepOnFold());
+ }
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index df95a91d72d7..b83931fa0615 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,6 +35,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
+import android.text.ClientFlags;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -1540,8 +1541,21 @@ public class Paint {
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
+ return setTypefaceInternal(typeface, true);
+ }
+
+ private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
nSetTypeface(mNativePaint, typefaceNative);
+
+ if (ClientFlags.clearFontVariationSettings()) {
+ if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
+ // We cannot call setFontVariationSetting with empty string or null because it calls
+ // setTypeface method. To avoid recursive setTypeface call, manually resetting
+ // mFontVariationSettings.
+ mFontVariationSettings = null;
+ }
+ }
mTypeface = typeface;
return typeface;
}
@@ -2037,6 +2051,14 @@ public class Paint {
* </li>
* </ul>
*
+ * Note: This method replaces the Typeface previously set to this instance.
+ * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
+ * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
+ * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
+ * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
+ * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
+ * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
+ *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
@@ -2059,8 +2081,8 @@ public class Paint {
if (settings == null || settings.length() == 0) {
mFontVariationSettings = null;
- setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
- Collections.emptyList()));
+ setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()), false);
return true;
}
@@ -2078,7 +2100,8 @@ public class Paint {
return false;
}
mFontVariationSettings = settings;
- setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
+ setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
+ false);
return true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index ecf47209a802..7f11feaa585e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -39,6 +39,8 @@ import androidx.window.extensions.embedding.SplitController;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import com.android.window.flags.Flags;
+
import java.util.Objects;
@@ -55,11 +57,9 @@ class WindowExtensionsImpl implements WindowExtensions {
*/
private static final int NO_LEVEL_OVERRIDE = -1;
- /**
- * The min version of the WM Extensions that must be supported in the current platform version.
- */
- @VisibleForTesting
- static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 6;
+ private static final int EXTENSIONS_VERSION_V7 = 7;
+
+ private static final int EXTENSIONS_VERSION_V6 = 6;
private final Object mLock = new Object();
private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
@@ -67,7 +67,6 @@ class WindowExtensionsImpl implements WindowExtensions {
private volatile SplitController mSplitController;
private volatile WindowAreaComponent mWindowAreaComponent;
- private final int mVersion = EXTENSIONS_VERSION_CURRENT_PLATFORM;
private final boolean mIsActivityEmbeddingEnabled;
WindowExtensionsImpl() {
@@ -76,9 +75,22 @@ class WindowExtensionsImpl implements WindowExtensions {
Log.i(TAG, generateLogMessage());
}
+ /**
+ * The min version of the WM Extensions that must be supported in the current platform version.
+ */
+ @VisibleForTesting
+ static int getExtensionsVersionCurrentPlatform() {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ // Activity Embedding animation customization is the only major feature for v7.
+ return EXTENSIONS_VERSION_V7;
+ } else {
+ return EXTENSIONS_VERSION_V6;
+ }
+ }
+
private String generateLogMessage() {
final StringBuilder logBuilder = new StringBuilder("Initializing Window Extensions, "
- + "vendor API level=" + mVersion);
+ + "vendor API level=" + getExtensionsVersionCurrentPlatform());
final int levelOverride = getLevelOverride();
if (levelOverride != NO_LEVEL_OVERRIDE) {
logBuilder.append(", override to ").append(levelOverride);
@@ -91,7 +103,12 @@ class WindowExtensionsImpl implements WindowExtensions {
@Override
public int getVendorApiLevel() {
final int levelOverride = getLevelOverride();
- return (levelOverride != NO_LEVEL_OVERRIDE) ? levelOverride : mVersion;
+ return hasLevelOverride() ? levelOverride : getExtensionsVersionCurrentPlatform();
+ }
+
+ @VisibleForTesting
+ boolean hasLevelOverride() {
+ return getLevelOverride() != NO_LEVEL_OVERRIDE;
}
private int getLevelOverride() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index ea60b1531a3f..f1e7ef5ce123 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -1359,4 +1359,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration,
windowLayoutInfo);
}
+
+ @VisibleForTesting
+ @NonNull
+ static String positionToString(@ContainerPosition int position) {
+ return switch (position) {
+ case CONTAINER_POSITION_LEFT -> "left";
+ case CONTAINER_POSITION_TOP -> "top";
+ case CONTAINER_POSITION_RIGHT -> "right";
+ case CONTAINER_POSITION_BOTTOM -> "bottom";
+ default -> "Unknown position:" + position;
+ };
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index 61ea51a35f58..139ddda5af3c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -32,6 +32,7 @@ android_test {
],
static_libs: [
+ "TestParameterInjector",
"androidx.window.extensions",
"androidx.window.extensions.core_core",
"junit",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index c5aaddc4e0ed..92f48141b607 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -16,7 +16,7 @@
package androidx.window.extensions;
-import static androidx.window.extensions.WindowExtensionsImpl.EXTENSIONS_VERSION_CURRENT_PLATFORM;
+import static androidx.window.extensions.WindowExtensionsImpl.getExtensionsVersionCurrentPlatform;
import static com.google.common.truth.Truth.assertThat;
@@ -59,7 +59,8 @@ public class WindowExtensionsTest {
@Test
public void testGetVendorApiLevel_extensionsEnabled_matchesCurrentVersion() {
assumeTrue(WindowManager.hasWindowExtensionsEnabled());
- assertThat(mVersion).isEqualTo(EXTENSIONS_VERSION_CURRENT_PLATFORM);
+ assumeFalse(((WindowExtensionsImpl) mExtensions).hasLevelOverride());
+ assertThat(mVersion).isEqualTo(getExtensionsVersionCurrentPlatform());
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 325750243744..1c4c8870b26f 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -35,6 +35,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition;
+import static androidx.window.extensions.embedding.SplitPresenter.positionToString;
import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
@@ -78,7 +79,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
@@ -86,6 +86,9 @@ import androidx.window.extensions.layout.WindowLayoutInfo;
import com.android.window.flags.Flags;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -108,7 +111,7 @@ import java.util.List;
@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
public class OverlayPresentationTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@@ -875,57 +878,70 @@ public class OverlayPresentationTest {
eq(overlayContainer.getTaskFragmentToken()), eq(activityToken));
}
- // TODO(b/243518738): Rewrite with TestParameter.
- @Test
- public void testGetOverlayPosition() {
- assertWithMessage("It must be position left for left overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.left,
- TASK_BOUNDS.top,
- TASK_BOUNDS.right / 2,
- TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
- assertWithMessage("It must be position left for shrunk left overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.left,
- TASK_BOUNDS.top + 20,
- TASK_BOUNDS.right / 2,
- TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
- assertWithMessage("It must be position left for top overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.left,
- TASK_BOUNDS.top,
- TASK_BOUNDS.right,
- TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
- assertWithMessage("It must be position left for shrunk top overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.left + 20,
- TASK_BOUNDS.top,
- TASK_BOUNDS.right - 20,
- TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
- assertWithMessage("It must be position left for right overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.right / 2,
- TASK_BOUNDS.top,
- TASK_BOUNDS.right,
- TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
- assertWithMessage("It must be position left for shrunk right overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.right / 2,
- TASK_BOUNDS.top + 20,
- TASK_BOUNDS.right,
- TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
- assertWithMessage("It must be position left for bottom overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.left,
- TASK_BOUNDS.bottom / 2,
- TASK_BOUNDS.right,
- TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
- assertWithMessage("It must be position left for shrunk bottom overlay.")
- .that(getOverlayPosition(new Rect(
- TASK_BOUNDS.left + 20,
- TASK_BOUNDS.bottom / 20,
- TASK_BOUNDS.right - 20,
- TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
+ @Test
+ public void testGetOverlayPosition(@TestParameter OverlayPositionTestParams params) {
+ final Rect taskBounds = new Rect(TASK_BOUNDS);
+ final Rect overlayBounds = params.toOverlayBounds();
+ final int overlayPosition = getOverlayPosition(overlayBounds, taskBounds);
+
+ assertWithMessage("The overlay position must be "
+ + positionToString(params.mPosition) + ", but is "
+ + positionToString(overlayPosition)
+ + ", parent bounds=" + taskBounds + ", overlay bounds=" + overlayBounds)
+ .that(overlayPosition).isEqualTo(params.mPosition);
+ }
+
+ private enum OverlayPositionTestParams {
+ LEFT_OVERLAY(CONTAINER_POSITION_LEFT, false /* shouldBeShrunk */),
+ LEFT_SHRUNK_OVERLAY(CONTAINER_POSITION_LEFT, true /* shouldBeShrunk */),
+ TOP_OVERLAY(CONTAINER_POSITION_TOP, false /* shouldBeShrunk */),
+ TOP_SHRUNK_OVERLAY(CONTAINER_POSITION_TOP, true /* shouldBeShrunk */),
+ RIGHT_OVERLAY(CONTAINER_POSITION_RIGHT, false /* shouldBeShrunk */),
+ RIGHT_SHRUNK_OVERLAY(CONTAINER_POSITION_RIGHT, true /* shouldBeShrunk */),
+ BOTTOM_OVERLAY(CONTAINER_POSITION_BOTTOM, false /* shouldBeShrunk */),
+ BOTTOM_SHRUNK_OVERLAY(CONTAINER_POSITION_BOTTOM, true /* shouldBeShrunk */);
+
+ @SplitPresenter.ContainerPosition
+ private final int mPosition;
+
+ private final boolean mShouldBeShrunk;
+
+ OverlayPositionTestParams(
+ @SplitPresenter.ContainerPosition int position, boolean shouldBeShrunk) {
+ mPosition = position;
+ mShouldBeShrunk = shouldBeShrunk;
+ }
+
+ @NonNull
+ private Rect toOverlayBounds() {
+ Rect r = new Rect(TASK_BOUNDS);
+ final int offset = mShouldBeShrunk ? 20 : 0;
+ switch (mPosition) {
+ case CONTAINER_POSITION_LEFT:
+ r.top += offset;
+ r.right /= 2;
+ r.bottom -= offset;
+ break;
+ case CONTAINER_POSITION_TOP:
+ r.left += offset;
+ r.right -= offset;
+ r.bottom /= 2;
+ break;
+ case CONTAINER_POSITION_RIGHT:
+ r.left = r.right / 2;
+ r.top += offset;
+ r.bottom -= offset;
+ break;
+ case CONTAINER_POSITION_BOTTOM:
+ r.left += offset;
+ r.right -= offset;
+ r.top = r.bottom / 2;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid position: " + mPosition);
+ }
+ return r;
+ }
}
/**
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index e6cb3a08119a..5135e9ee14bc 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -239,6 +239,9 @@ android_library {
"wmshell.protolog.json.gz",
"wmshell.protolog.pb",
],
+ flags_packages: [
+ "com_android_wm_shell_flags",
+ ],
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 52ae93f5ebf1..bbbc23e8b922 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -34,6 +34,7 @@
<activity
android:name=".bubbles.shortcut.CreateBubbleShortcutActivity"
+ android:featureFlag="com.android.wm.shell.enable_retrievable_bubbles"
android:exported="true"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.NoDisplay"
@@ -47,6 +48,7 @@
<activity
android:name=".bubbles.shortcut.ShowBubblesActivity"
+ android:featureFlag="com.android.wm.shell.enable_retrievable_bubbles"
android:exported="true"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.NoDisplay" >
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index f0d80a02243a..d3fc49bcb766 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -39,8 +39,6 @@ enum class DesktopModeFlags(
/**
* Determines state of flag based on the actual flag and desktop mode developer option overrides.
- *
- * Note, this method makes sure that a constant developer toggle overrides is read until reboot.
*/
fun isEnabled(context: Context): Boolean =
if (!Flags.showDesktopWindowingDevOption() ||
@@ -65,7 +63,7 @@ enum class DesktopModeFlags(
?: run {
val override = getToggleOverrideFromSystem(context)
// Cache toggle override the first time we encounter context. Override does not change
- // with context, as context is just used to fetch System Property and Settings.Global
+ // with context, as context is just used to fetch Settings.Global
cachedToggleOverride = override
Log.d(TAG, "Toggle override initialized to: $override")
override
@@ -74,29 +72,13 @@ enum class DesktopModeFlags(
return override
}
- private fun getToggleOverrideFromSystem(context: Context): ToggleOverride {
- // A non-persistent System Property is used to store override to ensure it remains
- // constant till reboot.
- val overrideFromSystemProperties: ToggleOverride? =
- System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride()
- return overrideFromSystemProperties
- ?: run {
- // Read Setting Global if System Property is not present (just after reboot)
- // or not valid (user manually changed the value)
- val overrideFromSettingsGlobal =
- convertToToggleOverrideWithFallback(
- Settings.Global.getInt(
- context.contentResolver,
- Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
- ToggleOverride.OVERRIDE_UNSET.setting),
- ToggleOverride.OVERRIDE_UNSET)
- // Initialize System Property
- System.setProperty(
- SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString())
-
- overrideFromSettingsGlobal
- }
- }
+ private fun getToggleOverrideFromSystem(context: Context): ToggleOverride =
+ convertToToggleOverrideWithFallback(
+ Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+ ToggleOverride.OVERRIDE_UNSET.setting),
+ ToggleOverride.OVERRIDE_UNSET)
/**
* Override state of desktop mode developer option toggle.
@@ -113,27 +95,12 @@ enum class DesktopModeFlags(
OVERRIDE_ON(1)
}
- private fun String?.convertToToggleOverride(): ToggleOverride? {
- val intValue = this?.toIntOrNull() ?: return null
- return settingToToggleOverrideMap[intValue]
- ?: run {
- Log.w(TAG, "Unknown toggleOverride int $intValue")
- null
- }
- }
-
companion object {
private const val TAG = "DesktopModeFlags"
/**
- * Key for non-persistent System Property which is used to store desktop windowing developer
- * option overrides.
- */
- private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
-
- /**
* Local cache for toggle override, which is initialized once on its first access. It needs to
- * be refreshed only on reboots as overridden state takes effect on reboots.
+ * be refreshed only on reboots as overridden state is expected to take effect on reboots.
*/
private var cachedToggleOverride: ToggleOverride? = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index f7a5c271a729..d4d9d003bc0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.bubbles;
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -225,8 +225,7 @@ public class BubbleExpandedView extends LinearLayout {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
options.setPendingIntentBackgroundActivityStartMode(
- MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
Intent fillInIntent = new Intent();
// Apply flags to make behaviour match documentLaunchMode=always.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index c79d9c4942bf..5e2141aa639e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -15,7 +15,7 @@
*/
package com.android.wm.shell.bubbles;
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -103,8 +103,7 @@ public class BubbleTaskViewHelper {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
options.setPendingIntentBackgroundActivityStartMode(
- MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
Intent fillInIntent = new Intent();
// Apply flags to make behaviour match documentLaunchMode=always.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 64a1b0c804da..140d7765e5c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -30,6 +30,7 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Size;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
@@ -42,9 +43,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -69,26 +68,36 @@ public class PipBoundsState {
@Retention(RetentionPolicy.SOURCE)
public @interface StashType {}
+ public static final int NAMED_KCA_LAUNCHER_SHELF = 0;
+ public static final int NAMED_KCA_TABLETOP_MODE = 1;
+
+ @IntDef(prefix = { "NAMED_KCA_" }, value = {
+ NAMED_KCA_LAUNCHER_SHELF,
+ NAMED_KCA_TABLETOP_MODE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NamedKca {}
+
private static final String TAG = PipBoundsState.class.getSimpleName();
- private final @NonNull Rect mBounds = new Rect();
- private final @NonNull Rect mMovementBounds = new Rect();
- private final @NonNull Rect mNormalBounds = new Rect();
- private final @NonNull Rect mExpandedBounds = new Rect();
- private final @NonNull Rect mNormalMovementBounds = new Rect();
- private final @NonNull Rect mExpandedMovementBounds = new Rect();
- private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState;
+ @NonNull private final Rect mBounds = new Rect();
+ @NonNull private final Rect mMovementBounds = new Rect();
+ @NonNull private final Rect mNormalBounds = new Rect();
+ @NonNull private final Rect mExpandedBounds = new Rect();
+ @NonNull private final Rect mNormalMovementBounds = new Rect();
+ @NonNull private final Rect mExpandedMovementBounds = new Rect();
+ @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
- private final @NonNull Context mContext;
+ @NonNull private final Context mContext;
private float mAspectRatio;
private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset;
- private @Nullable PipReentryState mPipReentryState;
+ @Nullable private PipReentryState mPipReentryState;
private final LauncherState mLauncherState = new LauncherState();
- private final @NonNull SizeSpecSource mSizeSpecSource;
- private @Nullable ComponentName mLastPipComponentName;
- private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
+ @NonNull private final SizeSpecSource mSizeSpecSource;
+ @Nullable private ComponentName mLastPipComponentName;
+ @NonNull private final MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
private boolean mIsShelfShowing;
@@ -120,12 +129,18 @@ public class PipBoundsState {
* as unrestricted keep clear area. Values in this map would be appended to
* {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
*/
- private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
+ private final SparseArray<Rect> mNamedUnrestrictedKeepClearAreas = new SparseArray<>();
- private @Nullable Runnable mOnMinimalSizeChangeCallback;
- private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
- private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- private List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>();
+ @Nullable private Runnable mOnMinimalSizeChangeCallback;
+ @Nullable private TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
+ private final List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
+ private final List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>();
+
+ /**
+ * This is used to set the launcher shelf height ahead of non-auto-enter-pip animation,
+ * to avoid the race condition. See also {@link #NAMED_KCA_LAUNCHER_SHELF}.
+ */
+ public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect();
// the size of the current bounds relative to the max size spec
private float mBoundsScale;
@@ -430,17 +445,32 @@ public class PipBoundsState {
mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
}
- /** Add a named unrestricted keep clear area. */
- public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
- mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+ /** Set a named unrestricted keep clear area. */
+ public void setNamedUnrestrictedKeepClearArea(
+ @NamedKca int tag, @Nullable Rect unrestrictedArea) {
+ if (unrestrictedArea == null) {
+ mNamedUnrestrictedKeepClearAreas.remove(tag);
+ } else {
+ mNamedUnrestrictedKeepClearAreas.put(tag, unrestrictedArea);
+ if (tag == NAMED_KCA_LAUNCHER_SHELF) {
+ mCachedLauncherShelfHeightKeepClearArea.set(unrestrictedArea);
+ }
+ }
}
- /** Remove a named unrestricted keep clear area. */
- public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
- mNamedUnrestrictedKeepClearAreas.remove(name);
+ /**
+ * Forcefully set the keep-clear-area for launcher shelf height if applicable.
+ * This is used for entering PiP in button navigation mode to make sure the destination bounds
+ * calculation includes the shelf height, to avoid race conditions that such callback is sent
+ * from Launcher after the entering animation is started.
+ */
+ public void mayUseCachedLauncherShelfHeight() {
+ if (!mCachedLauncherShelfHeightKeepClearArea.isEmpty()) {
+ setNamedUnrestrictedKeepClearArea(
+ NAMED_KCA_LAUNCHER_SHELF, mCachedLauncherShelfHeightKeepClearArea);
+ }
}
-
/**
* @return restricted keep clear areas.
*/
@@ -454,9 +484,12 @@ public class PipBoundsState {
*/
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
- if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+ if (mNamedUnrestrictedKeepClearAreas.size() == 0) return mUnrestrictedKeepClearAreas;
final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
- unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+ for (int i = 0; i < mNamedUnrestrictedKeepClearAreas.size(); i++) {
+ final int key = mNamedUnrestrictedKeepClearAreas.keyAt(i);
+ unrestrictedAreas.add(mNamedUnrestrictedKeepClearAreas.get(key));
+ }
return unrestrictedAreas;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e713af6a5311..700742acfb43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -211,6 +211,7 @@ public abstract class WMShellModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
IWindowManager windowManager,
ShellCommandHandler shellCommandHandler,
@@ -229,6 +230,7 @@ public abstract class WMShellModule {
mainExecutor,
mainHandler,
mainChoreographer,
+ bgExecutor,
shellInit,
shellCommandHandler,
windowManager,
@@ -246,6 +248,7 @@ public abstract class WMShellModule {
context,
mainHandler,
mainExecutor,
+ bgExecutor,
mainChoreographer,
windowManager,
shellInit,
@@ -366,13 +369,14 @@ public abstract class WMShellModule {
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
MultiInstanceHelper multiInstanceHelper,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
transactionPool, iconProvider, recentTasks, launchAdjacentController,
windowDecorViewModel, desktopTasksController, null /* stageCoordinator */,
- multiInstanceHelper, mainExecutor);
+ multiInstanceHelper, mainExecutor, mainHandler);
}
//
@@ -530,7 +534,8 @@ public abstract class WMShellModule {
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
- Optional<RecentTasksController> recentTasksController) {
+ Optional<RecentTasksController> recentTasksController,
+ InteractionJankMonitor interactionJankMonitor) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler,
@@ -538,7 +543,8 @@ public abstract class WMShellModule {
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper,
- mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null));
+ mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null),
+ interactionJankMonitor);
}
@WMSingleton
@@ -564,9 +570,10 @@ public abstract class WMShellModule {
Context context,
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Optional<DesktopTasksLimiter> desktopTasksLimiter) {
+ Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ InteractionJankMonitor interactionJankMonitor) {
return new DragToDesktopTransitionHandler(context, transitions,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer, interactionJankMonitor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 1a9c304e2aee..037fbb235bd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -206,12 +206,13 @@ public abstract class Pip1Module {
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 066b5ad39d0f..73aa7ceea68d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -347,7 +347,7 @@ class DesktopModeLoggerTransitionObserver(
else -> {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "Unknown enter reason for transition type ${transitionInfo.type}",
+ "Unknown enter reason for transition type: %s",
transitionInfo.type
)
EnterReason.UNKNOWN_ENTER
@@ -368,7 +368,7 @@ class DesktopModeLoggerTransitionObserver(
else -> {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "Unknown exit reason for transition type ${transitionInfo.type}",
+ "Unknown exit reason for transition type: %s",
transitionInfo.type
)
ExitReason.UNKNOWN_EXIT
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 1bf125938e6f..da212e704b24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -66,7 +66,7 @@ fun calculateInitialBounds(
idealSize
}
} else {
- maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio)
+ maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
}
}
ORIENTATION_PORTRAIT -> {
@@ -85,13 +85,13 @@ fun calculateInitialBounds(
} else {
if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
// Apply custom app width and calculate maximum size
- maximumSizeMaintainingAspectRatio(
+ maximizeSizeGivenAspectRatio(
taskInfo,
Size(customPortraitWidthForLandscapeApp, idealSize.height),
appAspectRatio
)
} else {
- maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio)
+ maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
}
}
}
@@ -107,7 +107,7 @@ fun calculateInitialBounds(
* Calculates the largest size that can fit in a given area while maintaining a specific aspect
* ratio.
*/
-fun maximumSizeMaintainingAspectRatio(
+fun maximizeSizeGivenAspectRatio(
taskInfo: RunningTaskInfo,
targetArea: Size,
aspectRatio: Float
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9e6099f2e4cc..9fd2c274df2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -49,6 +49,9 @@ import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
@@ -69,6 +72,7 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -122,7 +126,8 @@ class DesktopTasksController(
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor,
private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
- private val recentTasksController: RecentTasksController?
+ private val recentTasksController: RecentTasksController?,
+ private val interactionJankMonitor: InteractionJankMonitor
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -377,12 +382,15 @@ class DesktopTasksController(
fun startDragToDesktop(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
+ taskSurface: SurfaceControl,
) {
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: startDragToDesktop taskId=%d",
taskInfo.taskId
)
+ interactionJankMonitor.begin(taskSurface, context,
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
dragToDesktopValueAnimator
@@ -671,7 +679,7 @@ class DesktopTasksController(
} else {
// if non-resizable then calculate max bounds according to aspect ratio
val activityAspectRatio = calculateAspectRatio(taskInfo)
- val newSize = maximumSizeMaintainingAspectRatio(taskInfo,
+ val newSize = maximizeSizeGivenAspectRatio(taskInfo,
Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
val newBounds = centerInArea(
newSize, stableBounds, stableBounds.left, stableBounds.top)
@@ -818,9 +826,8 @@ class DesktopTasksController(
val intent = Intent(context, DesktopWallpaperActivity::class.java)
val options =
ActivityOptions.makeBasic().apply {
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val pendingIntent =
PendingIntent.getActivity(
@@ -1080,7 +1087,6 @@ class DesktopTasksController(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
val targetWindowingMode =
@@ -1090,9 +1096,6 @@ class DesktopTasksController(
} else {
WINDOWING_MODE_FREEFORM
}
- if (Flags.enableWindowingDynamicInitialBounds()) {
- wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
- }
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
wct.reorder(taskInfo.token, true /* onTop */)
if (useDesktopOverrideDensity()) {
@@ -1339,33 +1342,42 @@ class DesktopTasksController(
*
* @param taskInfo the task being dragged.
* @param y height of drag, to be checked against status bar height.
+ * @return the [IndicatorType] used for the resulting transition
*/
fun onDragPositioningEndThroughStatusBar(
inputCoordinates: PointF,
taskInfo: RunningTaskInfo,
- ) {
- val indicator = getVisualIndicator() ?: return
+ taskSurface: SurfaceControl,
+ ): IndicatorType {
+ // End the drag_hold CUJ interaction.
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+ val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR
val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ IndicatorType.TO_DESKTOP_INDICATOR -> {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
+ ?: return IndicatorType.NO_INDICATOR
+ // Start a new jank interaction for the drag release to desktop window animation.
+ interactionJankMonitor.begin(taskSurface, context,
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
if (Flags.enableWindowingDynamicInitialBounds()) {
finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
} else {
finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
}
}
- DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
- DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ IndicatorType.NO_INDICATOR,
+ IndicatorType.TO_FULLSCREEN_INDICATOR -> {
cancelDragToDesktop(taskInfo)
}
- DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
requestSplit(taskInfo, leftOrTop = true)
}
- DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
requestSplit(taskInfo, leftOrTop = false)
}
}
+ return indicatorType
}
/** Update the exclusion region for a specified task */
@@ -1426,7 +1438,6 @@ class DesktopTasksController(
setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
)
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
}
val wct = WindowContainerTransaction()
wct.sendPendingIntent(launchIntent, null, opts.toBundle())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index ddee8fac8f44..9e79eddb0e59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -30,6 +30,9 @@ import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -57,17 +60,20 @@ class DragToDesktopTransitionHandler(
private val context: Context,
private val transitions: Transitions,
private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+ private val interactionJankMonitor: InteractionJankMonitor,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {
constructor(
context: Context,
transitions: Transitions,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ interactionJankMonitor: InteractionJankMonitor
) : this(
context,
transitions,
rootTaskDisplayAreaOrganizer,
+ interactionJankMonitor,
Supplier { SurfaceControl.Transaction() }
)
@@ -567,6 +573,8 @@ class DragToDesktopTransitionHandler(
onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
startTransitionFinishCb.onTransitionFinished(null /* null */)
clearState()
+ interactionJankMonitor.end(
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
}
}
)
@@ -604,6 +612,10 @@ class DragToDesktopTransitionHandler(
"DragToDesktop: onTransitionConsumed() start transition aborted"
)
state.startAborted = true
+ // Cancel CUJ interaction if the transition is aborted.
+ interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+ } else if (state.cancelTransitionToken != transition) {
+ interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 95fe8b6f1f4e..7e0362475f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.draganddrop;
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -280,8 +280,7 @@ public class DragAndDropPolicy {
baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
// Put BAL flags to avoid activity start aborted.
baseActivityOpts.setPendingIntentBackgroundActivityStartMode(
- MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- baseActivityOpts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
final Bundle opts = baseActivityOpts.toBundle();
if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index a749019046f8..b27c428f1693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,10 +16,12 @@
package com.android.wm.shell.pip;
+import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -69,9 +71,10 @@ public interface Pip {
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * @return {@link PipTransitionController} instance.
+ * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
+ * started / finished callbacks.
*/
- default PipTransitionController getPipTransitionController() {
- return null;
- }
+ default void registerPipTransitionCallback(
+ @NonNull PipTransitionController.PipTransitionCallback callback,
+ @NonNull Executor executor) { }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index a8346a9b3b48..852382ddfba1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -230,6 +230,7 @@ public class PipAnimationController {
/**
* Quietly cancel the animator by removing the listeners first.
+ * TODO(b/275003573): deprecate this, cancelling without the proper callbacks is problematic.
*/
static void quietCancel(@NonNull ValueAnimator animator) {
animator.removeAllUpdateListeners();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8d63ff2a3a5d..723a53128bd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,7 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(
+ mPipTransitionCallback, mMainExecutor);
}
}
@@ -495,7 +496,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ }
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
@@ -2023,7 +2026,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
removeContentOverlay(mPipOverlay, null /* callback */);
}
if (animator != null) {
- PipAnimationController.quietCancel(animator);
+ animator.cancel();
mPipAnimationController.resetAnimatorState();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e5633de2a3a2..a52141c5f9d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1020,6 +1020,9 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.attach(leash);
}
+ // Make sure we have the launcher shelf into destination bounds calculation
+ // before the animator starts.
+ mPipBoundsState.mayUseCachedLauncherShelfHeight();
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
@@ -1173,7 +1176,13 @@ public class PipTransition extends PipTransitionController {
.setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
}
- final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ // Both Shell and Launcher calculate their own "adjusted" source-rect-hint values based on
+ // appBounds being source bounds when entering PiP.
+ final Rect sourceBounds = swipePipToHomeOverlay == null
+ ? pipTaskInfo.configuration.windowConfiguration.getBounds()
+ : mPipOrganizer.mAppBounds;
+
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index b1dd4f1c1395..fc9e2becf98c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,8 +53,9 @@ import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Responsible supplying PiP Transitions.
@@ -66,7 +67,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+ private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -183,16 +184,20 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(PipTransitionCallback callback) {
- mPipTransitionCallbacks.add(callback);
+ public void registerPipTransitionCallback(
+ @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
+ mPipTransitionCallbacks.put(callback, executor);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionStarted(direction, pipBounds);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "sendOnPipTransitionStarted direction=%d, bounds=%s", direction, pipBounds);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -209,9 +214,12 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionFinished(direction);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "sendOnPipTransitionFinished direction=%d", direction);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionFinished(direction));
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -228,9 +236,12 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionCanceled(direction);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "sendOnPipTransitionCancelled direction=%d", direction);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionCanceled(direction));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 26b7e58bc602..7451d2251588 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,6 +106,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -478,7 +479,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -645,9 +646,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
});
mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
- final String tag = "tabletop-mode";
if (!isInTabletopMode) {
- mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_TABLETOP_MODE, null);
return;
}
@@ -656,14 +657,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb
if (mTabletopModeController.getPreferredHalfInTabletopMode()
== TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
// Prefer top, avoid the bottom half of the display.
- mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
- displayBounds.left, displayBounds.centerY(),
- displayBounds.right, displayBounds.bottom));
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect(
+ displayBounds.left, displayBounds.centerY(),
+ displayBounds.right, displayBounds.bottom));
} else {
// Prefer bottom, avoid the top half of the display.
- mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
- displayBounds.left, displayBounds.top,
- displayBounds.right, displayBounds.centerY()));
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect(
+ displayBounds.left, displayBounds.top,
+ displayBounds.right, displayBounds.centerY()));
}
// Try to move the PiP window if we have entered PiP mode.
@@ -915,10 +918,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
0, mPipBoundsState.getDisplayBounds().bottom - height,
mPipBoundsState.getDisplayBounds().right,
mPipBoundsState.getDisplayBounds().bottom);
- mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
updatePipPositionForKeepClearAreas();
} else {
- mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
// postpone moving in response to hide of Launcher in case there's another change
mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
mMainExecutor.executeDelayed(
@@ -967,8 +972,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
int launcherRotation, Rect hotseatKeepClearArea) {
// preemptively add the keep clear area for Hotseat, so that it is taken into account
// when calculating the entry destination bounds of PiP window
- mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
- hotseatKeepClearArea);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
// cache current min/max size
Point minSize = mPipBoundsState.getMinSize();
@@ -1220,8 +1225,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public PipTransitionController getPipTransitionController() {
- return mPipTransitionController;
+ public void registerPipTransitionCallback(
+ PipTransitionController.PipTransitionCallback callback,
+ Executor executor) {
+ mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
+ callback, executor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index e8d6576bab3b..df3803d54d9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,6 +38,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -47,6 +48,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -171,7 +173,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -183,7 +187,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 62c0944f230b..0ed5079b7fba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
new file mode 100644
index 000000000000..8a9302bcfc98
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.pip2.animation;
+
+import android.animation.Animator;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Animator that handles bounds animations for entering / exiting PIP.
+ */
+public class PipEnterExitAnimator extends ValueAnimator
+ implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+ @IntDef(prefix = {"BOUNDS_"}, value = {
+ BOUNDS_ENTER,
+ BOUNDS_EXIT
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BOUNDS {}
+
+ public static final int BOUNDS_ENTER = 0;
+ public static final int BOUNDS_EXIT = 1;
+
+ @NonNull private final SurfaceControl mLeash;
+ private final SurfaceControl.Transaction mStartTransaction;
+ private final int mEnterAnimationDuration;
+ private final @BOUNDS int mDirection;
+
+ // optional callbacks for tracking animation start and end
+ @Nullable private Runnable mAnimationStartCallback;
+ @Nullable private Runnable mAnimationEndCallback;
+
+ private final Rect mBaseBounds = new Rect();
+ private final Rect mStartBounds = new Rect();
+ private final Rect mEndBounds = new Rect();
+
+ // Bounds updated by the evaluator as animator is running.
+ private final Rect mAnimatedRect = new Rect();
+
+ private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ mSurfaceControlTransactionFactory;
+ private final RectEvaluator mRectEvaluator;
+ private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
+
+ public PipEnterExitAnimator(Context context,
+ @NonNull SurfaceControl leash,
+ SurfaceControl.Transaction startTransaction,
+ @NonNull Rect baseBounds,
+ @NonNull Rect startBounds,
+ @NonNull Rect endBounds,
+ @BOUNDS int direction) {
+ mLeash = leash;
+ mStartTransaction = startTransaction;
+ mBaseBounds.set(baseBounds);
+ mStartBounds.set(startBounds);
+ mAnimatedRect.set(startBounds);
+ mEndBounds.set(endBounds);
+ mRectEvaluator = new RectEvaluator(mAnimatedRect);
+ mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context);
+ mDirection = direction;
+
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+ mEnterAnimationDuration = context.getResources()
+ .getInteger(R.integer.config_pipEnterAnimationDuration);
+
+ setDuration(mEnterAnimationDuration);
+ setEvaluator(mRectEvaluator);
+ addListener(this);
+ addUpdateListener(this);
+ }
+
+ public void setAnimationStartCallback(@NonNull Runnable runnable) {
+ mAnimationStartCallback = runnable;
+ }
+
+ public void setAnimationEndCallback(@NonNull Runnable runnable) {
+ mAnimationEndCallback = runnable;
+ }
+
+ @Override
+ public void onAnimationStart(@NonNull Animator animation) {
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation) {
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+ // TODO (b/350801661): implement fixed rotation
+
+ mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, null,
+ mBaseBounds, mAnimatedRect, null, isInPipDirection(), fraction)
+ .round(tx, mLeash, isInPipDirection())
+ .shadow(tx, mLeash, isInPipDirection());
+ tx.apply();
+ }
+
+ private boolean isInPipDirection() {
+ return mDirection == BOUNDS_ENTER;
+ }
+
+ // no-ops
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(@NonNull Animator animation) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 683d30d59b33..33703ad95a9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -53,6 +53,7 @@ import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipContentOverlay;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
+import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -378,12 +379,34 @@ public class PipTransition extends PipTransitionController implements
if (pipChange == null) {
return false;
}
- // cache the PiP task token and leash
+
WindowContainerToken pipTaskToken = pipChange.getContainer();
+ if (pipTaskToken == null) {
+ return false;
+ }
- startTransaction.apply();
- // TODO: b/275910498 Use a new implementation of the PiP animator here.
- finishCallback.onTransitionFinished(null);
+ WindowContainerTransaction finishWct = new WindowContainerTransaction();
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+
+ Rect startBounds = pipChange.getStartAbsBounds();
+ Rect endBounds = pipChange.getEndAbsBounds();
+ SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+
+ PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
+ startTransaction, startBounds, startBounds, endBounds,
+ PipEnterExitAnimator.BOUNDS_ENTER);
+
+ tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
+ this::onClientDrawAtTransitionEnd);
+ finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
+
+ animator.setAnimationEndCallback(() -> {
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
+ });
+
+ animator.start();
return true;
}
@@ -421,10 +444,25 @@ public class PipTransition extends PipTransitionController implements
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- startTransaction.apply();
- // TODO: b/275910498 Use a new implementation of the PiP animator here.
- finishCallback.onTransitionFinished(null);
- mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+
+ Rect startBounds = pipChange.getStartAbsBounds();
+ Rect endBounds = pipChange.getEndAbsBounds();
+ SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+
+ PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
+ startTransaction, startBounds, startBounds, endBounds,
+ PipEnterExitAnimator.BOUNDS_EXIT);
+ animator.setAnimationEndCallback(() -> {
+ finishCallback.onTransitionFinished(null);
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+ });
+
+ animator.start();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 8df287d12cbc..06c57bd7092d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -113,6 +113,9 @@ public interface SplitScreen {
/** Called when device waking up finished. */
void onFinishedWakingUp();
+ /** Called when device starts going to sleep (screen off). */
+ void onStartedGoingToSleep();
+
/** Called when requested to go to fullscreen from the current active split app. */
void goToFullscreenFromSplit();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e659151fee7f..b8575565ef8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -50,6 +50,7 @@ import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -180,6 +181,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final LauncherApps mLauncherApps;
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
@@ -227,7 +229,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
Optional<DesktopTasksController> desktopTasksController,
@Nullable StageCoordinator stageCoordinator,
MultiInstanceHelper multiInstanceHelper,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
@@ -236,6 +239,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
@@ -292,7 +296,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
- mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController,
+ mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
mWindowDecorViewModel);
}
@@ -448,13 +452,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
boolean animatingDismiss) {
- mStageCoordinator.onKeyguardVisibilityChanged(visible);
+ mStageCoordinator.onKeyguardStateChanged(visible, occluded);
}
public void onFinishedWakingUp() {
mStageCoordinator.onFinishedWakingUp();
}
+ public void onStartedGoingToSleep() {
+ mStageCoordinator.onStartedGoingToSleep();
+ }
+
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
}
@@ -1201,6 +1209,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void onStartedGoingToSleep() {
+ mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
+ }
+
+ @Override
public void goToFullscreenFromSplit() {
mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index b3dab8527617..48d17ec6963f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -68,6 +68,7 @@ class SplitScreenTransitions {
DismissSession mPendingDismiss = null;
EnterSession mPendingEnter = null;
TransitSession mPendingResize = null;
+ TransitSession mPendingRemotePassthrough = null;
private IBinder mAnimatingTransition = null;
private OneShotRemoteHandler mActiveRemoteHandler = null;
@@ -320,6 +321,11 @@ class SplitScreenTransitions {
return mPendingResize != null && mPendingResize.mTransition == transition;
}
+ boolean isPendingPassThrough(IBinder transition) {
+ return mPendingRemotePassthrough != null &&
+ mPendingRemotePassthrough.mTransition == transition;
+ }
+
@Nullable
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
@@ -331,6 +337,9 @@ class SplitScreenTransitions {
} else if (isPendingResize(transition)) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition");
return mPendingResize;
+ } else if (isPendingPassThrough(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved passThrough transition");
+ return mPendingRemotePassthrough;
}
return null;
}
@@ -378,6 +387,19 @@ class SplitScreenTransitions {
extraTransitType, resizeAnim);
}
+ /** Sets a transition to enter split. */
+ void setRemotePassThroughTransition(@NonNull IBinder transition,
+ @Nullable RemoteTransition remoteTransition) {
+ mPendingRemotePassthrough = new TransitSession(
+ transition, null, null,
+ remoteTransition, Transitions.TRANSIT_SPLIT_PASSTHROUGH);
+
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced remote passthrough split screen");
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setRemotePassThrough: transitType=%d remote=%s",
+ Transitions.TRANSIT_SPLIT_PASSTHROUGH, remoteTransition);
+ }
+
/** Starts a transition to dismiss split. */
IBinder startDismissTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@@ -474,6 +496,12 @@ class SplitScreenTransitions {
mPendingResize.onConsumed(aborted);
mPendingResize = null;
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition");
+ } else if (isPendingPassThrough(transition)) {
+ mPendingRemotePassthrough.onConsumed(aborted);
+ mPendingRemotePassthrough.mRemoteHandler.onTransitionConsumed(transition, aborted,
+ finishT);
+ mPendingRemotePassthrough = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition");
}
// TODO: handle transition consumed for active remote handler
@@ -495,6 +523,10 @@ class SplitScreenTransitions {
mPendingResize.onFinished(wct, mFinishTransaction);
mPendingResize = null;
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition");
+ } else if (isPendingPassThrough(mAnimatingTransition)) {
+ mPendingRemotePassthrough.onFinished(wct, mFinishTransaction);
+ mPendingRemotePassthrough = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for passThrough transition");
}
mActiveRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 99bd96e722fb..d7ee563b562c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.splitscreen;
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -92,6 +92,7 @@ import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.Debug;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -119,6 +120,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.policy.FoldLockSettingsObserver;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
@@ -191,7 +193,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private SplitLayout mSplitLayout;
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
- private boolean mKeyguardShowing;
+ private boolean mKeyguardActive;
private boolean mShowDecorImmediately;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -205,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
// Cache live tile tasks while entering recents, evict them from stages in finish transaction
// if user is opening another task(s).
private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
@@ -233,7 +236,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean mIsExiting;
private boolean mIsRootTranslucent;
@VisibleForTesting
- int mTopStageAfterFoldDismiss;
+ @StageType int mLastActiveStage;
+ private boolean mBreakOnNextWake;
+ /** Used to get the Settings value for "Continue using apps on fold". */
+ private FoldLockSettingsObserver mFoldLockSettingsObserver;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
@@ -313,9 +319,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
- TransactionPool transactionPool,
- IconProvider iconProvider, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
+ TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor,
+ Handler mainHandler, Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel) {
mContext = context;
@@ -324,6 +329,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer = taskOrganizer;
mLogger = new SplitscreenEventLogger();
mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
mRecentTasks = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
@@ -366,6 +372,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// With shell transition, we should update recents tile each callback so set this to true by
// default.
mShouldUpdateRecents = ENABLE_SHELL_TRANSITIONS;
+ mFoldLockSettingsObserver =
+ new FoldLockSettingsObserver(mainHandler, context);
+ mFoldLockSettingsObserver.register();
}
@VisibleForTesting
@@ -373,9 +382,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
DisplayController displayController, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
- Transitions transitions, TransactionPool transactionPool,
- ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
+ Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
+ Handler mainHandler, Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel) {
mContext = context;
@@ -393,6 +401,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
this::onTransitionAnimationComplete, this);
mLogger = new SplitscreenEventLogger();
mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
mRecentTasks = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
@@ -400,6 +409,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
+ mFoldLockSettingsObserver =
+ new FoldLockSettingsObserver(context.getMainThreadHandler(), context);
+ mFoldLockSettingsObserver.register();
}
public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -1504,51 +1516,80 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
+ /**
+ * Runs when keyguard state changes. The booleans here are a bit complicated, so for reference:
+ * @param active {@code true} if we are in a state where the keyguard *should* be shown
+ * -- still true when keyguard is "there" but is behind an app, or
+ * screen is off.
+ * @param occludingTaskRunning {@code true} when there is a running task that has
+ * FLAG_SHOW_WHEN_LOCKED -- also true when the task is
+ * just running on its own and keyguard is not active
+ * at all.
+ */
+ void onKeyguardStateChanged(boolean active, boolean occludingTaskRunning) {
+ mKeyguardActive = active;
if (!mMainStage.isActive()) {
return;
}
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing);
- setDividerVisibility(!mKeyguardShowing, null);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "onKeyguardVisibilityChanged: active=%b occludingTaskRunning=%b",
+ active, occludingTaskRunning);
+ setDividerVisibility(!mKeyguardActive, null);
+
+ if (active && occludingTaskRunning) {
+ dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ }
}
void onFinishedWakingUp() {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
- if (!mMainStage.isActive()) {
+ if (mBreakOnNextWake) {
+ dismissSplitKeepingLastActiveStage(EXIT_REASON_DEVICE_FOLDED);
+ }
+ }
+
+ void onStartedGoingToSleep() {
+ recordLastActiveStage();
+ }
+
+ /**
+ * Records the user's last focused stage -- main stage or side stage. Used to determine which
+ * stage of a split pair should be kept, in cases where system focus has moved elsewhere.
+ */
+ void recordLastActiveStage() {
+ if (!isSplitActive() || !isSplitScreenVisible()) {
+ mLastActiveStage = STAGE_TYPE_UNDEFINED;
+ } else if (mMainStage.isFocused()) {
+ mLastActiveStage = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mLastActiveStage = STAGE_TYPE_SIDE;
+ }
+ }
+
+ /**
+ * Dismisses split, keeping the app that the user focused last in split screen. If the user was
+ * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we
+ * will do a no-op.
+ */
+ void dismissSplitKeepingLastActiveStage(@ExitReason int reason) {
+ if (!mMainStage.isActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) {
+ // no-op
return;
}
- // Check if there's only one stage visible while keyguard occluded.
- final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
- final boolean oneStageVisible =
- mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
- if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
- // Dismiss split because there's show-when-locked activity showing on top of keyguard.
- // Also make sure the task contains show-when-locked activity remains on top after split
- // dismissed.
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- }
-
- // Dismiss split if the flag record any side of stages.
- if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- if (ENABLE_SHELL_TRANSITIONS) {
- // Need manually clear here due to this transition might be aborted due to keyguard
- // on top and lead to no visible change.
- clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(wct, this,
- mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
- setSplitsVisible(false);
- } else {
- exitSplitScreen(
- mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
- }
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Need manually clear here due to this transition might be aborted due to keyguard
+ // on top and lead to no visible change.
+ clearSplitPairedInRecents(reason);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mLastActiveStage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
+ setSplitsVisible(false);
+ } else {
+ exitSplitScreen(mLastActiveStage == STAGE_TYPE_MAIN ? mMainStage : mSideStage, reason);
}
+
+ mBreakOnNextWake = false;
}
void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -1909,8 +1950,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
// will be canceled.
- options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
// TODO (b/336477473): Disallow enter PiP when launching a task in split by default;
// this might have to be changed as more split-to-pip cujs are defined.
@@ -2223,11 +2264,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
"setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s",
- visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller());
+ visible, mKeyguardActive, mIsDividerRemoteAnimating, Debug.getCaller());
// Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
// dismissing animation.
- if (visible && mKeyguardShowing) {
+ if (visible && mKeyguardActive) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Defer showing divider bar due to keyguard showing.");
return;
@@ -2597,21 +2638,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
void onFoldedStateChanged(boolean folded) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded);
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
- if (!folded) return;
-
- if (!isSplitActive() || !isSplitScreenVisible()) return;
- // To avoid split dismiss when user fold the device and unfold to use later, we only
- // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
- // when user interact on phone folded.
- if (mMainStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
- } else if (mSideStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ if (folded) {
+ recordLastActiveStage();
+ // If user folds and has the setting "Continue using apps on fold = NEVER", we assume
+ // they don't want to continue using split on the outer screen (i.e. we break split if
+ // they wake the device in its folded state).
+ mBreakOnNextWake = willSleepOnFold();
+ } else {
+ mBreakOnNextWake = false;
}
}
+ /** Returns true if the phone will sleep when it folds. */
+ @VisibleForTesting
+ boolean willSleepOnFold() {
+ return mFoldLockSettingsObserver != null && mFoldLockSettingsObserver.isSleepOnFold();
+ }
+
private Rect getSideStageBounds() {
return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
@@ -2666,7 +2710,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
- if (isSplitScreenVisible()) {
+ if (isSplitActive()) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation",
request.getDebugId());
// Check if the display is rotating.
@@ -2676,6 +2720,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& displayChange.getStartRotation() != displayChange.getEndRotation()) {
mSplitLayout.setFreezeDividerWindow(true);
}
+ if (request.getRemoteTransition() != null) {
+ mSplitTransitions.setRemotePassThroughTransition(transition,
+ request.getRemoteTransition());
+ }
// Still want to monitor everything while in split-screen, so return non-null.
return new WindowContainerTransaction();
} else {
@@ -3002,6 +3050,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
notifySplitAnimationFinished();
return true;
}
+ } else if (mSplitTransitions.isPendingPassThrough(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startAnimation: passThrough transition=%d", info.getDebugId());
+ mSplitTransitions.mPendingRemotePassthrough.mRemoteHandler.startAnimation(transition,
+ info, startTransaction, finishTransaction, finishCallback);
+ notifySplitAnimationFinished();
+ return true;
}
return startPendingAnimation(transition, info, startTransaction, finishTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index e330f3ab65ab..b65e97899f3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -33,7 +33,6 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
@@ -88,7 +87,8 @@ public class TvSplitScreenController extends SplitScreenController {
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor);
+ Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor,
+ mainHandler);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 79476919221e..81ca48fa6b3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -56,7 +56,7 @@ public class TvStageCoordinator extends StageCoordinator
SystemWindows systemWindows) {
super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- mainExecutor, recentTasks, launchAdjacentController, Optional.empty());
+ mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty());
mTvSplitMenuController = new TvSplitMenuController(context, this,
systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
index 5c814dcc9b16..bad5baf24651 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -73,7 +73,7 @@ class WindowlessSnapshotWindowCreator {
final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
new StartingSurfaceDrawer.WindowlessStartingWindow(
- runningTaskInfo.configuration, rootSurface);
+ mContext.getResources().getConfiguration(), rootSurface);
final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
mContext, display, wlw, "WindowlessSnapshotWindowCreator");
final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index 98a803128587..f3725579bf48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -76,7 +76,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
}
final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
new StartingSurfaceDrawer.WindowlessStartingWindow(
- taskInfo.configuration, rootSurface);
+ mContext.getResources().getConfiguration(), rootSurface);
final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
myContext, display, wlw, "WindowlessSplashWindowCreator");
final String title = "Windowless Splash " + taskInfo.taskId;
@@ -95,7 +95,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
}
final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ mSplashscreenContentDrawer.createViewContextWrapper(myContext));
viewHost.setView(rootLayout, lp);
final int bgColor = taskDescription.getBackgroundColor();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index dd4595a70211..287e779d8e24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -48,6 +48,7 @@ public class ShellInit {
public ShellInit(ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
+ ProtoLog.registerGroups(ShellProtoLogGroup.values());
}
/**
@@ -76,7 +77,6 @@ public class ShellInit {
*/
@VisibleForTesting
public void init() {
- ProtoLog.registerGroups(ShellProtoLogGroup.values());
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fc8b1d27ad02..874cca523ad1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -190,6 +190,9 @@ public class Transitions implements RemoteCallable<Transitions>,
// TRANSIT_FIRST_CUSTOM + 17
TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
+ /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */
+ public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18;
+
/** Transition type for desktop mode transitions. */
public static final int TRANSIT_DESKTOP_MODE_TYPES =
WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b9cb6d3d5007..5c230c0a505c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -56,6 +56,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -72,6 +73,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final IWindowManager mWindowManager;
private final Context mContext;
private final Handler mMainHandler;
+ private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final ShellExecutor mMainExecutor;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
@@ -108,6 +110,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
ShellExecutor shellExecutor,
Choreographer mainChoreographer,
IWindowManager windowManager,
@@ -120,6 +123,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
+ mBgExecutor = bgExecutor;
mWindowManager = windowManager;
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
@@ -289,6 +293,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
taskInfo,
taskSurface,
mMainHandler,
+ mBgExecutor,
mMainChoreographer,
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 7e1b973a98f4..cf42a49f7103 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLarge
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
@@ -48,7 +49,9 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -58,6 +61,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
*/
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private final Handler mHandler;
+ private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
@@ -78,10 +82,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface);
mHandler = handler;
+ mBgExecutor = bgExecutor;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
}
@@ -218,6 +224,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
relayoutParams.mOccludingCaptionElements.add(controlsElement);
}
+ @SuppressLint("MissingPermission")
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
@@ -235,7 +242,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
- mTaskOrganizer.applyTransaction(wct);
+ mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 53976251e31d..0e8fd7c7d0a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_APP_BROWSER;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
@@ -36,6 +38,8 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -43,11 +47,8 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -98,6 +99,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -132,6 +134,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final ShellController mShellController;
private final Context mContext;
private final Handler mMainHandler;
+ private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
@@ -183,6 +186,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellExecutor shellExecutor,
Handler mainHandler,
Choreographer mainChoreographer,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
@@ -201,6 +205,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
shellExecutor,
mainHandler,
mainChoreographer,
+ bgExecutor,
shellInit,
shellCommandHandler,
windowManager,
@@ -225,6 +230,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellExecutor shellExecutor,
Handler mainHandler,
Choreographer mainChoreographer,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
@@ -245,6 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
+ mBgExecutor = bgExecutor;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
mShellController = shellController;
@@ -321,7 +328,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) return;
final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
- if (taskInfo.displayId != oldTaskInfo.displayId) {
+ if (taskInfo.displayId != oldTaskInfo.displayId
+ && !Flags.enableAdditionalWindowsAboveStatusBar()) {
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
@@ -385,7 +393,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
decoration.close();
final int displayId = taskInfo.displayId;
- if (mEventReceiversByDisplay.contains(displayId)) {
+ if (mEventReceiversByDisplay.contains(displayId)
+ && !Flags.enableAdditionalWindowsAboveStatusBar()) {
removeTaskFromEventReceiver(displayId);
}
// Remove the decoration from the cache last because WindowDecoration#close could still
@@ -424,19 +433,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
private void openInBrowser(Uri uri) {
- final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
- .setComponent(getDefaultBrowser())
+ final Intent intent = Intent.makeMainSelectorActivity(ACTION_MAIN, CATEGORY_APP_BROWSER)
+ .setData(uri)
.addFlags(FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
- private ComponentName getDefaultBrowser() {
- final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
- final ResolveInfo info = mContext.getPackageManager()
- .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- return info.getComponentInfo().getComponentName();
- }
-
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -517,6 +519,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
+ // When the app enters split-select, the handle will no longer be visible, meaning
+ // we shouldn't receive input for it any longer.
+ decoration.disposeStatusBarInputLayer();
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
} else if (id == R.id.open_in_browser_button) {
// TODO(b/346441962): let the decoration handle the click gesture and only call back
@@ -653,13 +658,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- return false;
+ && !taskInfo.isFreeform()) {
+ return handleNonFreeformMotionEvent(decoration, v, e);
+ } else {
+ return handleFreeformMotionEvent(decoration, taskInfo, v, e);
}
+ }
+
+ private boolean handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration,
+ View v, MotionEvent e) {
+ final int id = v.getId();
+ if (id == R.id.caption_handle) {
+ if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // Caption handle is located within the status bar region, meaning the
+ // DisplayPolicy will attempt to transfer this input to status bar if it's
+ // a swipe down. Pilfer here to keep the gesture in handle alone.
+ mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ }
+ handleCaptionThroughStatusBar(e, decoration);
+ final boolean wasDragging = mIsDragging;
+ updateDragStatus(e.getActionMasked());
+ // Only prevent onClick from receiving this event if it's a drag.
+ return wasDragging;
+ }
+ return false;
+ }
+
+ private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration,
+ RunningTaskInfo taskInfo, View v, MotionEvent e) {
+ final int id = v.getId();
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
- final int id = v.getId();
final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
|| id == R.id.open_menu_button);
switch (e.getActionMasked()) {
@@ -668,7 +698,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
- mIsDragging = false;
+ updateDragStatus(e.getActionMasked());
mHasLongClicked = false;
// Do not consume input event if a button is touched, otherwise it would
// prevent the button's ripple effect from showing.
@@ -688,7 +718,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
decoration.mTaskSurface,
e.getRawX(dragPointerIdx),
newTaskBounds);
- mIsDragging = true;
+ updateDragStatus(e.getActionMasked());
return true;
}
case MotionEvent.ACTION_UP:
@@ -718,7 +748,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
// onClick call that results.
return false;
} else {
- mIsDragging = false;
+ updateDragStatus(e.getActionMasked());
return true;
}
}
@@ -726,6 +756,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return true;
}
+ private void updateDragStatus(int eventAction) {
+ switch (eventAction) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ mIsDragging = false;
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ mIsDragging = true;
+ break;
+ }
+ }
+ }
+
/**
* Perform a task size toggle on release of the double-tap, assuming no drag event
* was handled during the double-tap.
@@ -850,6 +895,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
*
* @param relevantDecor the window decoration of the focused task's caption. This method only
* handles motion events outside this caption's bounds.
+ * TODO(b/349135068): Outside-touch detection no longer works with the
+ * enableAdditionalWindowsAboveStatusBar flag enabled. This
+ * will be fixed once we can add FLAG_WATCH_OUTSIDE_TOUCH to relevant menus,
+ * at which point, all EventReceivers and external touch logic should be removed.
*/
private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
@@ -902,9 +951,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
-
- if (dragFromStatusBarAllowed
- && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
+ final boolean shouldStartTransitionDrag =
+ relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
+ || Flags.enableAdditionalWindowsAboveStatusBar();
+ if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
mTransitionDragActive = true;
}
break;
@@ -918,8 +968,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
// Though this isn't a hover event, we need to update handle's hover state
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
- mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
+ DesktopModeVisualIndicator.IndicatorType resultType =
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface);
+ // If we are entering split select, handle will no longer be visible and
+ // should not be receiving any input.
+ if (resultType == TO_SPLIT_LEFT_INDICATOR
+ || resultType != TO_SPLIT_RIGHT_INDICATOR) {
+ relevantDecor.disposeStatusBarInputLayer();
+ }
mMoveToDesktopAnimator = null;
return;
} else {
@@ -931,7 +990,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
relevantDecor.checkTouchEvent(ev);
break;
}
-
case ACTION_MOVE: {
if (relevantDecor == null) {
return;
@@ -954,7 +1012,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator);
+ mMoveToDesktopAnimator, relevantDecor.mTaskSurface);
}
}
if (mMoveToDesktopAnimator != null) {
@@ -1091,10 +1149,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopModeWindowDecorFactory.create(
mContext,
mDisplayController,
+ mSplitScreenController,
mTaskOrganizer,
taskInfo,
taskSurface,
mMainHandler,
+ mBgExecutor,
mMainChoreographer,
mSyncQueue,
mRootTaskDisplayAreaOrganizer);
@@ -1132,7 +1192,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
- incrementEventReceiverTasks(taskInfo.displayId);
+ if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+ incrementEventReceiverTasks(taskInfo.displayId);
+ }
}
private RunningTaskInfo getOtherSplitTask(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5d662b20ebb9..529def7ca3d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,11 +24,13 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.ComponentName;
@@ -68,7 +70,9 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
@@ -95,8 +99,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
private final Handler mHandler;
+ private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
+ private final SplitScreenController mSplitScreenController;
private WindowDecorationViewHolder mWindowDecorViewHolder;
private View.OnClickListener mOnCaptionButtonClickListener;
@@ -147,27 +153,32 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
+ SplitScreenController splitScreenController,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
- this (context, displayController, taskOrganizer, taskInfo, taskSurface,
- handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
- SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
+ this (context, displayController, splitScreenController, taskOrganizer, taskInfo,
+ taskSurface, handler, bgExecutor, choreographer, syncQueue,
+ rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new,
+ SurfaceControl.Transaction::new, WindowContainerTransaction::new,
+ SurfaceControl::new, new SurfaceControlViewHostFactory() {},
+ DefaultMaximizeMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
+ SplitScreenController splitScreenController,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@@ -181,7 +192,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
surfaceControlViewHostFactory);
+ mSplitScreenController = splitScreenController;
mHandler = handler;
+ mBgExecutor = bgExecutor;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
@@ -327,6 +340,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mHandler.post(mCurrentViewHostRunnable);
}
+ @SuppressLint("MissingPermission")
private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
@@ -337,7 +351,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
if (isHandleMenuActive()) {
- mHandleMenu.relayout(startT);
+ mHandleMenu.relayout(startT, mResult.mCaptionX);
}
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
@@ -353,33 +367,47 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT");
- mTaskOrganizer.applyTransaction(wct);
+ mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
Trace.endSection();
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
+ disposeStatusBarInputLayer();
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
return;
}
if (oldRootView != mResult.mRootView) {
+ disposeStatusBarInputLayer();
mWindowDecorViewHolder = createViewHolder();
}
Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
- mWindowDecorViewHolder.bindData(mTaskInfo);
+
+ final Point position = new Point();
+ if (isAppHandle(mWindowDecorViewHolder)) {
+ position.set(determineHandlePosition());
+ }
+ mWindowDecorViewHolder.bindData(mTaskInfo,
+ position,
+ mResult.mCaptionWidth,
+ mResult.mCaptionHeight,
+ isCaptionVisible());
Trace.endSection();
if (!mTaskInfo.isFocused) {
closeHandleMenu();
closeMaximizeMenu();
}
-
updateDragResizeListener(oldDecorationSurface);
updateMaximizeMenu(startT);
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
+ private boolean isCaptionVisible() {
+ return mTaskInfo.isVisible && mIsCaptionVisible;
+ }
+
private void setCapturedLink(Uri capturedLink, long timeStamp) {
if (capturedLink == null
|| (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
@@ -461,12 +489,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ private Point determineHandlePosition() {
+ final Point position = new Point(mResult.mCaptionX, 0);
+ if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
+ == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ && mDisplayController.getDisplayLayout(mTaskInfo.displayId).isLandscape()
+ ) {
+ // If this is the right split task, add left stage's width.
+ final Rect leftStageBounds = new Rect();
+ mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
+ position.x += leftStageBounds.width();
+ }
+ return position;
+ }
+
+ /**
+ * Dispose of the view used to forward inputs in status bar region. Intended to be
+ * used any time handle is no longer visible.
+ */
+ void disposeStatusBarInputLayer() {
+ if (!isAppHandle(mWindowDecorViewHolder)
+ || !Flags.enableAdditionalWindowsAboveStatusBar()) {
+ return;
+ }
+ ((AppHandleViewHolder) mWindowDecorViewHolder).disposeStatusBarInputLayer();
+ }
+
private WindowDecorationViewHolder createViewHolder() {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
return new AppHandleViewHolder(
mResult.mRootView,
mOnCaptionTouchListener,
- mOnCaptionButtonClickListener
+ mOnCaptionButtonClickListener,
+ (v, event) -> {
+ updateHoverAndPressStatus(event);
+ return true;
+ }
);
} else if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_header) {
@@ -489,6 +547,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
throw new IllegalArgumentException("Unexpected layout resource id");
}
+ private boolean isAppHandle(WindowDecorationViewHolder viewHolder) {
+ return viewHolder instanceof AppHandleViewHolder;
+ }
+
@VisibleForTesting
static void updateRelayoutParams(
RelayoutParams relayoutParams,
@@ -863,7 +925,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
splitScreenController,
DesktopModeStatus.canEnterDesktopMode(mContext),
browserLinkAvailable(),
- mResult.mCaptionHeight
+ mResult.mCaptionWidth,
+ mResult.mCaptionHeight,
+ mResult.mCaptionX
);
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
@@ -955,10 +1019,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @return {@code true} if event is inside caption handle view, {@code false} if not
*/
boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
- if (isHandleMenuActive() || !(mWindowDecorViewHolder
- instanceof AppHandleViewHolder)) {
+ if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder)
+ || Flags.enableAdditionalWindowsAboveStatusBar()) {
return false;
}
+ // The status bar input layer can only receive input in handle coordinates to begin with,
+ // so checking coordinates is unnecessary as input is always within handle bounds.
+ if (isAppHandle(mWindowDecorViewHolder)
+ && Flags.enableAdditionalWindowsAboveStatusBar()
+ && isCaptionVisible()) {
+ return true;
+ }
return checkTouchEventInCaption(ev);
}
@@ -992,7 +1063,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @param ev the MotionEvent to compare
*/
void checkTouchEvent(MotionEvent ev) {
- if (mResult.mRootView == null) return;
+ if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return;
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
final boolean inHandle = !isHandleMenuActive()
@@ -1014,7 +1085,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @param ev the MotionEvent to compare against.
*/
void updateHoverAndPressStatus(MotionEvent ev) {
- if (mResult.mRootView == null) return;
+ if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return;
final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
final boolean inHandle = !isHandleMenuActive()
&& checkTouchEventInFocusedCaptionHandle(ev);
@@ -1024,15 +1095,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// We want handle to remain pressed if the pointer moves outside of it during a drag.
handle.setPressed((inHandle && action == ACTION_DOWN)
|| (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
- if (isHandleMenuActive() && !isHandleMenuAboveStatusBar()) {
+ if (isHandleMenuActive()) {
mHandleMenu.checkMotionEvent(ev);
}
}
- private boolean isHandleMenuAboveStatusBar() {
- return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform();
- }
-
private boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
@@ -1044,6 +1111,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
closeHandleMenu();
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
+ disposeStatusBarInputLayer();
clearCurrentViewHostRunnable();
super.close();
}
@@ -1143,20 +1211,24 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
DesktopModeWindowDecoration create(
Context context,
DisplayController displayController,
+ SplitScreenController splitScreenController,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
return new DesktopModeWindowDecoration(
context,
displayController,
+ splitScreenController,
taskOrganizer,
taskInfo,
taskSurface,
handler,
+ bgExecutor,
choreographer,
syncQueue,
rootTaskDisplayAreaOrganizer);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index da268988bac7..3fd3656ccbc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -19,7 +19,11 @@ package com.android.wm.shell.windowdecor;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import android.graphics.PointF;
@@ -43,7 +47,7 @@ class DragDetector {
private final PointF mInputDownPoint = new PointF();
private int mTouchSlop;
private boolean mIsDragEvent;
- private int mDragPointerId;
+ private int mDragPointerId = -1;
private boolean mResultOfDownAction;
@@ -67,7 +71,7 @@ class DragDetector {
*
* @return the result returned by {@link #mEventHandler}, or the result when
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
- */
+ */
boolean onMotionEvent(View v, MotionEvent ev) {
final boolean isTouchScreen =
(ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
@@ -86,10 +90,14 @@ class DragDetector {
return mResultOfDownAction;
}
case ACTION_MOVE: {
- if (ev.findPointerIndex(mDragPointerId) == -1) {
- mDragPointerId = ev.getPointerId(0);
+ if (mDragPointerId == -1) {
+ // The primary pointer was lifted, ignore the rest of the gesture.
+ return mResultOfDownAction;
}
final int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+ if (dragPointerIndex == -1) {
+ throw new IllegalStateException("Failed to find primary pointer!");
+ }
if (!mIsDragEvent) {
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
@@ -99,22 +107,52 @@ class DragDetector {
}
// The event handler should only be notified about 'move' events if a drag has been
// detected.
- if (mIsDragEvent) {
- return mEventHandler.handleMotionEvent(v, ev);
- } else {
+ if (!mIsDragEvent) {
return mResultOfDownAction;
}
+ return mEventHandler.handleMotionEvent(v,
+ getSinglePointerEvent(ev, mDragPointerId));
+ }
+ case ACTION_HOVER_ENTER:
+ case ACTION_HOVER_MOVE:
+ case ACTION_HOVER_EXIT: {
+ return mEventHandler.handleMotionEvent(v,
+ getSinglePointerEvent(ev, mDragPointerId));
+ }
+ case ACTION_POINTER_UP: {
+ if (mDragPointerId == -1) {
+ // The primary pointer was lifted, ignore the rest of the gesture.
+ return mResultOfDownAction;
+ }
+ if (mDragPointerId != ev.getPointerId(ev.getActionIndex())) {
+ // Ignore a secondary pointer being lifted.
+ return mResultOfDownAction;
+ }
+ // The primary pointer is being lifted.
+ final int dragPointerId = mDragPointerId;
+ mDragPointerId = -1;
+ return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId));
}
case ACTION_UP:
case ACTION_CANCEL: {
+ final int dragPointerId = mDragPointerId;
resetState();
- return mEventHandler.handleMotionEvent(v, ev);
+ if (dragPointerId == -1) {
+ // The primary pointer was lifted, ignore the rest of the gesture.
+ return mResultOfDownAction;
+ }
+ return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId));
}
default:
- return mEventHandler.handleMotionEvent(v, ev);
+ // Ignore other events.
+ return mResultOfDownAction;
}
}
+ private static MotionEvent getSinglePointerEvent(MotionEvent ev, int pointerId) {
+ return ev.getPointerCount() > 1 ? ev.split(1 << pointerId) : ev;
+ }
+
void setTouchSlop(int touchSlop) {
mTouchSlop = touchSlop;
}
@@ -129,4 +167,4 @@ class DragDetector {
interface MotionEventHandler {
boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index b51b700bc315..e174e83d11c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -69,7 +69,9 @@ class HandleMenu(
private val splitScreenController: SplitScreenController,
private val shouldShowWindowingPill: Boolean,
private val shouldShowBrowserPill: Boolean,
- private val captionHeight: Int
+ private val captionWidth: Int,
+ private val captionHeight: Int,
+ captionX: Int
) {
private val context: Context = parentDecor.mDecorWindowContext
private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
@@ -105,7 +107,7 @@ class HandleMenu(
private val globalMenuPosition: Point = Point()
init {
- updateHandleMenuPillPositions()
+ updateHandleMenuPillPositions(captionX)
}
fun show() {
@@ -262,11 +264,11 @@ class HandleMenu(
/**
* Updates handle menu's position variables to reflect its next position.
*/
- private fun updateHandleMenuPillPositions() {
+ private fun updateHandleMenuPillPositions(captionX: Int) {
val menuX: Int
val menuY: Int
val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds
- updateGlobalMenuPosition(taskBounds)
+ updateGlobalMenuPosition(taskBounds, captionX)
if (layoutResId == R.layout.desktop_mode_app_header) {
// Align the handle menu to the left side of the caption.
menuX = marginMenuStart
@@ -287,7 +289,8 @@ class HandleMenu(
handleMenuPosition.set(menuX.toFloat(), menuY.toFloat())
}
- private fun updateGlobalMenuPosition(taskBounds: Rect) {
+ private fun updateGlobalMenuPosition(taskBounds: Rect, captionX: Int) {
+ val nonFreeformX = captionX + (captionWidth / 2) - (menuWidth / 2)
when {
taskInfo.isFreeform -> {
globalMenuPosition.set(
@@ -297,7 +300,7 @@ class HandleMenu(
}
taskInfo.isFullscreen -> {
globalMenuPosition.set(
- /* x = */ taskBounds.width() / 2 - (menuWidth / 2),
+ /* x = */ nonFreeformX,
/* y = */ marginMenuTop
)
}
@@ -311,16 +314,13 @@ class HandleMenu(
when (splitPosition) {
SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
globalMenuPosition.set(
- /* x = */ leftOrTopStageBounds.width()
- + (rightOrBottomStageBounds.width() / 2)
- - (menuWidth / 2),
+ /* x = */ leftOrTopStageBounds.width() + nonFreeformX,
/* y = */ marginMenuTop
)
}
SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
globalMenuPosition.set(
- /* x = */ (leftOrTopStageBounds.width() / 2)
- - (menuWidth / 2),
+ /* x = */ nonFreeformX,
/* y = */ marginMenuTop
)
}
@@ -332,9 +332,12 @@ class HandleMenu(
/**
* Update pill layout, in case task changes have caused positioning to change.
*/
- fun relayout(t: SurfaceControl.Transaction) {
+ fun relayout(
+ t: SurfaceControl.Transaction,
+ captionX: Int
+ ) {
handleMenuViewContainer?.let { container ->
- updateHandleMenuPillPositions()
+ updateHandleMenuPillPositions(captionX)
container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index d212f2131ed4..a691f59a2155 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -139,7 +139,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
TaskDragResizer mTaskDragResizer;
- private boolean mIsCaptionVisible;
+ boolean mIsCaptionVisible;
/** The most recent set of insets applied to this window decoration. */
private WindowDecorationInsets mWindowDecorationInsets;
@@ -508,6 +508,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mTaskDragResizer = taskDragResizer;
}
+ // TODO(b/346441962): Move these three methods closer to implementing or View-level classes to
+ // keep implementation details more encapsulated.
private void setCaptionVisibility(View rootView, boolean visible) {
if (rootView == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 4897f76a20cf..6a354f10049b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -30,17 +30,22 @@ import android.view.WindowManager
*/
class AdditionalSystemViewContainer(
private val context: Context,
- layoutId: Int,
taskId: Int,
x: Int,
y: Int,
width: Int,
- height: Int
+ height: Int,
+ layoutId: Int? = null
) : AdditionalViewContainer() {
override val view: View
+ val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
init {
- view = LayoutInflater.from(context).inflate(layoutId, null)
+ if (layoutId != null) {
+ view = LayoutInflater.from(context).inflate(layoutId, null)
+ } else {
+ view = View(context)
+ }
val lp = WindowManager.LayoutParams(
width, height, x, y,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
@@ -51,12 +56,11 @@ class AdditionalSystemViewContainer(
gravity = Gravity.LEFT or Gravity.TOP
setTrustedOverlay()
}
- val wm: WindowManager? = context.getSystemService(WindowManager::class.java)
- wm?.addView(view, lp)
+ windowManager?.addView(view, lp)
}
override fun releaseView() {
- context.getSystemService(WindowManager::class.java)?.removeViewImmediate(view)
+ windowManager?.removeViewImmediate(view)
}
override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
@@ -64,6 +68,6 @@ class AdditionalSystemViewContainer(
this.x = x.toInt()
this.y = y.toInt()
}
- view.layoutParams = lp
+ windowManager?.updateViewLayout(view, lp)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 8d822c252288..76dfe37c7cc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -20,37 +20,68 @@ import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.res.ColorStateList
import android.graphics.Color
+import android.graphics.Point
+import android.view.SurfaceControl
import android.view.View
+import android.view.View.OnClickListener
+import android.view.View.OnHoverListener
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowManager
import android.widget.ImageButton
+import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
* It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
*/
internal class AppHandleViewHolder(
- rootView: View,
- onCaptionTouchListener: View.OnTouchListener,
- onCaptionButtonClickListener: View.OnClickListener
+ rootView: View,
+ private val onCaptionTouchListener: View.OnTouchListener,
+ private val onCaptionButtonClickListener: OnClickListener,
+ private val onCaptionHoverListener: OnHoverListener,
) : WindowDecorationViewHolder(rootView) {
companion object {
private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
}
-
+ private lateinit var taskInfo: RunningTaskInfo
+ private val windowManager = context.getSystemService(WindowManager::class.java)
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
+ // An invisible View that takes up the same coordinates as captionHandle but is layered
+ // above the status bar. The purpose of this View is to receive input intended for
+ // captionHandle.
+ private var statusBarInputLayer: AdditionalSystemViewContainer? = null
+
init {
captionView.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnClickListener(onCaptionButtonClickListener)
+ captionHandle.setOnHoverListener(onCaptionHoverListener)
}
- override fun bindData(taskInfo: RunningTaskInfo) {
+ override fun bindData(
+ taskInfo: RunningTaskInfo,
+ position: Point,
+ width: Int,
+ height: Int,
+ isCaptionVisible: Boolean
+ ) {
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
+ this.taskInfo = taskInfo
+ if (!isCaptionVisible && hasStatusBarInputLayer()) {
+ disposeStatusBarInputLayer()
+ return
+ }
+ if (hasStatusBarInputLayer()) {
+ updateStatusBarInputLayer(position)
+ } else {
+ createStatusBarInputLayer(position, width, height)
+ }
}
override fun onHandleMenuOpened() {
@@ -61,6 +92,45 @@ internal class AppHandleViewHolder(
animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
+ private fun createStatusBarInputLayer(handlePosition: Point,
+ handleWidth: Int,
+ handleHeight: Int) {
+ if (!Flags.enableAdditionalWindowsAboveStatusBar()) return
+ statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId,
+ handlePosition.x, handlePosition.y, handleWidth, handleHeight)
+ val view = statusBarInputLayer?.view
+ val lp = view?.layoutParams as WindowManager.LayoutParams
+ lp.title = "Handle Input Layer of task " + taskInfo.taskId
+ lp.setTrustedOverlay()
+ // Make this window a spy window to enable it to pilfer pointers from the system-wide
+ // gesture listener that receives events before window. This is to prevent notification
+ // shade gesture when we swipe down to enter desktop.
+ lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ view.id = R.id.caption_handle
+ view.setOnClickListener(onCaptionButtonClickListener)
+ view.setOnTouchListener(onCaptionTouchListener)
+ view.setOnHoverListener(onCaptionHoverListener)
+ windowManager.updateViewLayout(view, lp)
+ }
+
+ private fun updateStatusBarInputLayer(globalPosition: Point) {
+ statusBarInputLayer?.setPosition(SurfaceControl.Transaction(), globalPosition.x.toFloat(),
+ globalPosition.y.toFloat()) ?: return
+ }
+
+ private fun hasStatusBarInputLayer(): Boolean {
+ return statusBarInputLayer != null
+ }
+
+ /**
+ * Remove the input layer from [WindowManager]. Should be used when caption handle
+ * is not visible.
+ */
+ fun disposeStatusBarInputLayer() {
+ statusBarInputLayer?.releaseView()
+ statusBarInputLayer = null
+ }
+
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 46127b177bc3..b704d9c001c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -21,6 +21,7 @@ import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
+import android.graphics.Point
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
@@ -136,7 +137,13 @@ internal class AppHeaderViewHolder(
onMaximizeHoverAnimationFinishedListener
}
- override fun bindData(taskInfo: RunningTaskInfo) {
+ override fun bindData(
+ taskInfo: RunningTaskInfo,
+ position: Point,
+ width: Int,
+ height: Int,
+ isCaptionVisible: Boolean
+ ) {
if (Flags.enableThemedAppHeaders()) {
bindDataWithThemedHeaders(taskInfo)
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
index 5ae8d252a908..2341b099699f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
@@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor.viewholder
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
+import android.graphics.Point
import android.view.View
/**
@@ -30,7 +31,13 @@ internal abstract class WindowDecorationViewHolder(rootView: View) {
* A signal to the view holder that new data is available and that the views should be updated to
* reflect it.
*/
- abstract fun bindData(taskInfo: RunningTaskInfo)
+ abstract fun bindData(
+ taskInfo: RunningTaskInfo,
+ position: Point,
+ width: Int,
+ height: Int,
+ isCaptionVisible: Boolean
+ )
/** Callback when the handle menu is opened. */
abstract fun onHandleMenuOpened()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt
new file mode 100644
index 000000000000..b4cadf4f300b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+* Base test for opening recent apps overview from desktop mode.
+*
+* Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation.
+*/
+@Ignore("Base Test Class")
+abstract class SwitchToOverviewFromDesktop
+@JvmOverloads
+constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(navigationMode, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun switchToOverview() {
+ tapl.getLaunchedAppState().switchToOverview()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 6b6954289a34..a0408652a29b 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -37,6 +37,7 @@ android_test {
],
static_libs: [
+ "TestParameterInjector",
"WindowManager-Shell",
"junit",
"flag-junit",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 55b6bd278f20..bba9418db66a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -46,12 +46,14 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.window.TransitionInfo;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
import com.android.wm.shell.transition.TransitionInfoBuilder;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -59,6 +61,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Tests for {@link ActivityEmbeddingAnimationRunner}.
@@ -67,7 +70,7 @@ import java.util.ArrayList;
* atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
@Rule
@@ -204,15 +207,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
// TODO(b/243518738): Rewrite with TestParameter
@EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
@Test
- public void testCalculateParentBounds_flagEnabled() {
+ public void testCalculateParentBounds_flagEnabled_emptyParentSize() {
TransitionInfo.Change change;
final TransitionInfo.Change stubChange = createChange(0 /* flags */);
final Rect actualParentBounds = new Rect();
- Rect parentBounds = new Rect(0, 0, 2000, 2000);
- Rect endAbsBounds = new Rect(0, 0, 2000, 2000);
change = prepareChangeForParentBoundsCalculationTest(
new Point(0, 0) /* endRelOffset */,
- endAbsBounds,
+ new Rect(0, 0, 2000, 2000),
new Point() /* endParentSize */
);
@@ -220,69 +221,80 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
assertTrue("Parent bounds must be empty because end parent size is not set.",
actualParentBounds.isEmpty());
+ }
- String testString = "Parent start with (0, 0)";
- change = prepareChangeForParentBoundsCalculationTest(
- new Point(endAbsBounds.left - parentBounds.left,
- endAbsBounds.top - parentBounds.top),
- endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
-
- calculateParentBounds(change, stubChange, actualParentBounds);
-
- assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
- actualParentBounds);
-
- testString = "Container not start with (0, 0)";
- parentBounds = new Rect(0, 0, 2000, 2000);
- endAbsBounds = new Rect(1000, 500, 2000, 1500);
- change = prepareChangeForParentBoundsCalculationTest(
- new Point(endAbsBounds.left - parentBounds.left,
- endAbsBounds.top - parentBounds.top),
- endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
-
- calculateParentBounds(change, stubChange, actualParentBounds);
-
- assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
- actualParentBounds);
-
- testString = "Parent container on the right";
- parentBounds = new Rect(1000, 0, 2000, 2000);
- endAbsBounds = new Rect(1000, 500, 1500, 1500);
- change = prepareChangeForParentBoundsCalculationTest(
- new Point(endAbsBounds.left - parentBounds.left,
- endAbsBounds.top - parentBounds.top),
- endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
-
- calculateParentBounds(change, stubChange, actualParentBounds);
-
- assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
- actualParentBounds);
-
- testString = "Parent container on the bottom";
- parentBounds = new Rect(0, 1000, 2000, 2000);
- endAbsBounds = new Rect(500, 1500, 1500, 2000);
- change = prepareChangeForParentBoundsCalculationTest(
+ @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
+ @Test
+ public void testCalculateParentBounds_flagEnabled(
+ @TestParameter ParentBoundsTestParameters params) {
+ final TransitionInfo.Change stubChange = createChange(0 /*flags*/);
+ final Rect parentBounds = params.getParentBounds();
+ final Rect endAbsBounds = params.getEndAbsBounds();
+ final TransitionInfo.Change change = prepareChangeForParentBoundsCalculationTest(
new Point(endAbsBounds.left - parentBounds.left,
endAbsBounds.top - parentBounds.top),
endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+ final Rect actualParentBounds = new Rect();
calculateParentBounds(change, stubChange, actualParentBounds);
- assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
- actualParentBounds);
-
- testString = "Parent container in the middle";
- parentBounds = new Rect(500, 500, 1500, 1500);
- endAbsBounds = new Rect(1000, 500, 1500, 1000);
- change = prepareChangeForParentBoundsCalculationTest(
- new Point(endAbsBounds.left - parentBounds.left,
- endAbsBounds.top - parentBounds.top),
- endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
-
- calculateParentBounds(change, stubChange, actualParentBounds);
+ assertEquals(parentBounds, actualParentBounds);
+ }
- assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
- actualParentBounds);
+ private enum ParentBoundsTestParameters {
+ PARENT_START_WITH_0_0(
+ new int[]{0, 0, 2000, 2000},
+ new int[]{0, 0, 2000, 2000}),
+ CONTAINER_NOT_START_WITH_0_0(
+ new int[] {0, 0, 2000, 2000},
+ new int[] {1000, 500, 1500, 1500}),
+ PARENT_ON_THE_RIGHT(
+ new int[] {1000, 0, 2000, 2000},
+ new int[] {1000, 500, 1500, 1500}),
+ PARENT_ON_THE_BOTTOM(
+ new int[] {0, 1000, 2000, 2000},
+ new int[] {500, 1500, 1500, 2000}),
+ PARENT_IN_THE_MIDDLE(
+ new int[] {500, 500, 1500, 1500},
+ new int[] {1000, 500, 1500, 1000});
+
+ /**
+ * An int array to present {left, top, right, bottom} of the parent {@link Rect bounds}.
+ */
+ @NonNull
+ private final int[] mParentBounds;
+
+ /**
+ * An int array to present {left, top, right, bottom} of the absolute container
+ * {@link Rect bounds} after the transition finishes.
+ */
+ @NonNull
+ private final int[] mEndAbsBounds;
+
+ ParentBoundsTestParameters(
+ @NonNull int[] parentBounds, @NonNull int[] endAbsBounds) {
+ mParentBounds = parentBounds;
+ mEndAbsBounds = endAbsBounds;
+ }
+
+ @NonNull
+ private Rect getParentBounds() {
+ return asRect(mParentBounds);
+ }
+
+ @NonNull
+ private Rect getEndAbsBounds() {
+ return asRect(mEndAbsBounds);
+ }
+
+ @NonNull
+ private static Rect asRect(@NonNull int[] bounds) {
+ if (bounds.length != 4) {
+ throw new IllegalArgumentException("There must be exactly 4 elements in bounds, "
+ + "but found " + bounds.length + ": " + Arrays.toString(bounds));
+ }
+ return new Rect(bounds[0], bounds[1], bounds[2], bounds[3]);
+ }
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 6002c21ccb24..37510ef4ba21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -66,6 +66,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.MockToken
@@ -166,6 +167,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
@Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
@Mock lateinit var recentTasksController: RecentTasksController
+ @Mock
+ private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var mockSurface: SurfaceControl
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -248,7 +252,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
multiInstanceHelper,
shellExecutor,
Optional.of(desktopTasksLimiter),
- recentTasksController)
+ recentTasksController,
+ mockInteractionJankMonitor)
}
@After
@@ -580,138 +585,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
- val task = setUpFullscreenTask()
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
- val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
- val task =
- setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
- val task =
- setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
- val task =
- setUpFullscreenTask(
- isResizable = false,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
- shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
- val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2148,7 +2021,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFullscreenTask()
setUpLandscapeDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@@ -2164,7 +2037,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
setUpLandscapeDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@@ -2181,7 +2054,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
setUpLandscapeDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
}
@@ -2198,7 +2071,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
setUpLandscapeDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@@ -2218,7 +2091,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
shouldLetterbox = true)
setUpLandscapeDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
}
@@ -2234,7 +2107,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
setUpPortraitDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@@ -2253,7 +2126,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
setUpPortraitDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@@ -2273,7 +2146,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
shouldLetterbox = true)
setUpPortraitDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
}
@@ -2293,7 +2166,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
setUpPortraitDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@@ -2314,7 +2187,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
shouldLetterbox = true)
setUpPortraitDisplay()
- spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
+ spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface)
val wct = getLatestDragToDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index bbf523bc40d2..e4e2bd216c94 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -15,6 +15,7 @@ import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -51,6 +52,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var splitScreenController: SplitScreenController
@Mock private lateinit var dragAnimator: MoveToDesktopAnimator
+ @Mock
+ private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -63,7 +66,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
context,
transitions,
taskDisplayAreaOrganizer,
- transactionSupplier
+ mockInteractionJankMonitor,
+ transactionSupplier,
)
.apply { setSplitScreenController(splitScreenController) }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 6888de5472e2..75d21457b60b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index ace09a82d71c..66f8c0b9558d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 92762fa68550..6d18e3696f84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
index b1d62f485a2a..dd19d76d88cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -184,108 +184,6 @@ class DesktopModeFlagsTest : ShellTestCase() {
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() {
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
- setOverride(OVERRIDE_ON.setting)
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
- // Store System Property if not present
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_ON.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() {
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
- setOverride(OVERRIDE_UNSET.setting)
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
- // Store System Property if not present
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_UNSET.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() {
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
- setOverride(OVERRIDE_UNSET.setting)
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
- // Store System Property if not present
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_UNSET.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Suppress("ktlint:standard:max-line-length")
- fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc")
- setOverride(OVERRIDE_OFF.setting)
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
- // Store System Property if currently invalid
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_OFF.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Suppress("ktlint:standard:max-line-length")
- fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2")
- setOverride(OVERRIDE_OFF.setting)
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
- // Store System Property if currently invalid
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_OFF.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString())
- setOverride(OVERRIDE_ON.setting)
-
- // Have a consistent override until reboot
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_OFF.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString())
- setOverride(OVERRIDE_OFF.setting)
-
- // Have a consistent override until reboot
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_ON.setting.toString())
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Suppress("ktlint:standard:max-line-length")
- fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString())
- setOverride(OVERRIDE_OFF.setting)
-
- // Have a consistent override until reboot
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(OVERRIDE_UNSET.setting.toString())
- }
-
- @Test
@EnableFlags(
FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
@@ -445,12 +343,5 @@ class DesktopModeFlagsTest : ShellTestCase() {
DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
cachedToggleOverride.isAccessible = true
cachedToggleOverride.set(null, null)
-
- // Clear override cache stored in System property
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
- }
-
- private companion object {
- const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5b95b1588814..1c5d5e963156 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -50,7 +50,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
@@ -104,6 +104,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@Mock ShellExecutor mMainExecutor;
+ @Mock Handler mMainHandler;
@Mock DisplayController mDisplayController;
@Mock DisplayImeController mDisplayImeController;
@Mock DisplayInsetsController mDisplayInsetsController;
@@ -134,7 +135,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
- mStageCoordinator, mMultiInstanceHelper, mMainExecutor));
+ mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index a3009a55198f..29d3fb4cc04e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Handler;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -77,13 +78,13 @@ public class SplitTestUtils {
DisplayController displayController, DisplayImeController imeController,
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
- ShellExecutor mainExecutor,
+ ShellExecutor mainExecutor, Handler mainHandler,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
- transitions, transactionPool, mainExecutor, recentTasks,
+ transitions, transactionPool, mainExecutor, mainHandler, recentTasks,
launchAdjacentController, windowDecorViewModel);
// Prepare root task for testing.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 34b2eebb15a1..22b408cda0af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -49,9 +49,12 @@ import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.window.IRemoteTransition;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -107,6 +110,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private IconProvider mIconProvider;
@Mock private WindowDecorViewModel mWindowDecorViewModel;
@Mock private ShellExecutor mMainExecutor;
+ @Mock private Handler mMainHandler;
@Mock private LaunchAdjacentController mLaunchAdjacentController;
@Mock private DefaultMixedHandler mMixedHandler;
@Mock private SplitScreen.SplitInvocationListener mInvocationListener;
@@ -140,7 +144,7 @@ public class SplitTransitionTests extends ShellTestCase {
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
- mTransactionPool, mMainExecutor, Optional.empty(),
+ mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(),
mLaunchAdjacentController, Optional.empty());
mStageCoordinator.setMixedHandler(mMixedHandler);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
@@ -326,6 +330,32 @@ public class SplitTransitionTests extends ShellTestCase {
@Test
@UiThreadTest
+ public void testRemotePassThroughInvoked() throws RemoteException {
+ RemoteTransition remoteWrapper = mock(RemoteTransition.class);
+ IRemoteTransition remoteTransition = mock(IRemoteTransition.class);
+ IBinder remoteBinder = mock(IBinder.class);
+ doReturn(remoteBinder).when(remoteTransition).asBinder();
+ doReturn(remoteTransition).when(remoteWrapper).getRemoteTransition();
+
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_CHANGE, null,
+ remoteWrapper);
+ IBinder transition = mock(IBinder.class);
+ mMainStage.activate(new WindowContainerTransaction(), false);
+ mStageCoordinator.handleRequest(transition, request);
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .build();
+ boolean accepted = mStageCoordinator.startAnimation(transition, info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(Transitions.TransitionFinishCallback.class));
+ assertTrue(accepted);
+
+ verify(remoteTransition, times(1)).startAnimation(any(),
+ any(), any(), any());
+ }
+
+ @Test
+ @UiThreadTest
public void testEnterRecentsAndRestore() {
enterSplit();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d18fec2f24ad..eaef704b7d78 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -138,7 +138,8 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
- mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty()));
+ mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController,
+ Optional.empty()));
mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
@@ -347,8 +348,7 @@ public class StageCoordinatorTests extends ShellTestCase {
assertThat(options.getLaunchRootTask()).isEqualTo(mMainStage.mRootTaskInfo.token);
assertThat(options.getPendingIntentBackgroundActivityStartMode())
- .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission()).isTrue();
+ .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
}
@Test
@@ -359,10 +359,11 @@ public class StageCoordinatorTests extends ShellTestCase {
mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
when(mStageCoordinator.isSplitActive()).thenReturn(true);
when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
+ when(mStageCoordinator.willSleepOnFold()).thenReturn(true);
mStageCoordinator.onFoldedStateChanged(true);
- assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+ assertEquals(mStageCoordinator.mLastActiveStage, STAGE_TYPE_MAIN);
mStageCoordinator.onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index b1803e97b107..aeae0bebb697 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -30,6 +30,7 @@ import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
@@ -62,6 +63,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
@@ -71,6 +73,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -78,8 +81,6 @@ import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
-import java.util.Optional
-import java.util.function.Supplier
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -99,6 +100,8 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
+import java.util.Optional
+import java.util.function.Supplier
/**
* Tests of [DesktopModeWindowDecorViewModel]
@@ -122,6 +125,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockMainChoreographer: Choreographer
@Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockSplitScreenController: SplitScreenController
@Mock private lateinit var mockDisplayLayout: DisplayLayout
@Mock private lateinit var displayInsetsController: DisplayInsetsController
@Mock private lateinit var mockSyncQueue: SyncTransactionQueue
@@ -136,6 +140,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private val bgExecutor = TestShellExecutor()
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -155,6 +160,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockShellExecutor,
mockMainHandler,
mockMainChoreographer,
+ bgExecutor,
shellInit,
mockShellCommandHandler,
mockWindowManager,
@@ -171,7 +177,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockRootTaskDisplayAreaOrganizer,
windowDecorByTaskIdSpy, mockInteractionJankMonitor
)
-
+ desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
@@ -204,10 +210,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(mockDesktopModeWindowDecorFactory).create(
mContext,
mockDisplayController,
+ mockSplitScreenController,
mockTaskOrganizer,
task,
taskSurface,
mockMainHandler,
+ bgExecutor,
mockMainChoreographer,
mockSyncQueue,
mockRootTaskDisplayAreaOrganizer
@@ -228,10 +236,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(mockDesktopModeWindowDecorFactory, never()).create(
mContext,
mockDisplayController,
+ mockSplitScreenController,
mockTaskOrganizer,
task,
taskSurface,
mockMainHandler,
+ bgExecutor,
mockMainChoreographer,
mockSyncQueue,
mockRootTaskDisplayAreaOrganizer
@@ -243,10 +253,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(mockDesktopModeWindowDecorFactory, times(1)).create(
mContext,
mockDisplayController,
+ mockSplitScreenController,
mockTaskOrganizer,
task,
taskSurface,
mockMainHandler,
+ bgExecutor,
mockMainChoreographer,
mockSyncQueue,
mockRootTaskDisplayAreaOrganizer
@@ -254,6 +266,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testCreateAndDisposeEventReceiver() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
setUpMockDecorationForTask(task)
@@ -266,6 +279,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testEventReceiversOnMultipleDisplays() {
val secondaryDisplay = createVirtualDisplay() ?: return
val secondaryDisplayId = secondaryDisplay.display.displayId
@@ -344,7 +358,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskChanging(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+ any())
}
@Test
@@ -365,7 +380,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -382,7 +398,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+ any())
}
@Test
@@ -399,7 +416,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+ any(), any())
}
@Test
@@ -496,7 +514,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+ any())
} finally {
mockitoSession.finishMocking()
}
@@ -520,7 +539,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+ any())
} finally {
mockitoSession.finishMocking()
}
@@ -543,7 +563,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
+ any())
} finally {
mockitoSession.finishMocking()
}
@@ -682,7 +703,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
val decoration = mock(DesktopModeWindowDecoration::class.java)
whenever(
mockDesktopModeWindowDecorFactory.create(
- any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index d8606093ac5c..412fef30d4fb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -82,9 +82,12 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
@@ -127,6 +130,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private DisplayController mMockDisplayController;
@Mock
+ private SplitScreenController mMockSplitScreenController;
+ @Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
private Choreographer mMockChoreographer;
@@ -165,6 +170,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private SurfaceControl.Transaction mMockTransaction;
private StaticMockitoSession mMockitoSession;
private TestableContext mTestableContext;
+ private ShellExecutor mBgExecutor = new TestShellExecutor();
/** Set up run before test class. */
@BeforeClass
@@ -392,6 +398,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -418,6 +425,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -429,6 +437,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_fullscreenTask_postsViewHostCreation() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -457,6 +466,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_removesExistingHandlerCallback() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -471,6 +481,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void close_removesExistingHandlerCallback() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -656,8 +667,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ActivityManager.RunningTaskInfo taskInfo,
MaximizeMenuFactory maximizeMenuFactory) {
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
- mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
- mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+ mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer,
+ taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer,
+ mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
index 3fbab0f9e2bb..56224b4b4025 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -85,6 +85,23 @@ class DragDetectorTest {
}
@Test
+ fun testNoMove_mouse_passesDownAndUp() {
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_UP, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+ }
+
+ @Test
fun testMoveInSlop_touch_passesDownAndUp() {
`when`(eventHandler.handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN
@@ -166,6 +183,52 @@ class DragDetectorTest {
}
@Test
+ fun testDownMoveDown_shouldIgnoreTheSecondDownMotion() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ val newX = X + SLOP + 1
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testDownMouseMoveDownTouch_shouldIgnoreTheTouchDownMotion() {
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ val newX = X + SLOP + 1
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(any(), argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+ }
+
+ @Test
fun testPassesHoverEnter() {
`when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_HOVER_ENTER
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index adda9a688172..e548f8f1eb2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -130,7 +130,7 @@ class HandleMenuTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testFullscreenMenuUsesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED)
- val handleMenu = createAndShowHandleMenu()
+ val handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED)
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of display.
@@ -142,7 +142,7 @@ class HandleMenuTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testFreeformMenu_usesViewHostViewContainer() {
createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED)
- handleMenu = createAndShowHandleMenu()
+ handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED)
assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer)
// Verify menu is created near top-left of task.
val expected = Point(MENU_START_MARGIN, MENU_TOP_MARGIN)
@@ -153,7 +153,7 @@ class HandleMenuTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testSplitLeftMenu_usesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT)
- handleMenu = createAndShowHandleMenu()
+ handleMenu = createAndShowHandleMenu(SPLIT_POSITION_TOP_OR_LEFT)
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split left task.
@@ -168,7 +168,7 @@ class HandleMenuTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testSplitRightMenu_usesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT)
- handleMenu = createAndShowHandleMenu()
+ handleMenu = createAndShowHandleMenu(SPLIT_POSITION_BOTTOM_OR_RIGHT)
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split right task.
@@ -208,16 +208,30 @@ class HandleMenuTest : ShellTestCase() {
}
}
- private fun createAndShowHandleMenu(): HandleMenu {
+ private fun createAndShowHandleMenu(splitPosition: Int): HandleMenu {
val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
R.layout.desktop_mode_app_header
} else {
R.layout.desktop_mode_app_handle
}
+ val captionX = when (mockDesktopWindowDecoration.mTaskInfo.windowingMode) {
+ WINDOWING_MODE_FULLSCREEN -> (DISPLAY_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
+ WINDOWING_MODE_FREEFORM -> 0
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ (SPLIT_LEFT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
+ } else {
+ (SPLIT_RIGHT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
+ }
+ }
+ else -> error("Invalid windowing mode")
+ }
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
onClickListener, onTouchListener, appIcon, appName, displayController,
- splitScreenController, true /* shouldShowWindowingPill */,
- true /* shouldShowBrowserPill */, 50 /* captionHeight */)
+ splitScreenController, shouldShowWindowingPill = true,
+ shouldShowBrowserPill = true, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ captionX = captionX
+ )
handleMenu.show()
return handleMenu
}
@@ -233,5 +247,6 @@ class HandleMenuTest : ShellTestCase() {
private const val MENU_START_MARGIN = 20
private const val MENU_PILL_ELEVATION = 2
private const val MENU_PILL_SPACING_MARGIN = 4
+ private const val HANDLE_WIDTH = 80
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
index d3e996b12e1f..3b490550ab61 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -68,12 +68,12 @@ class AdditionalSystemViewContainerTest : ShellTestCase() {
fun testReleaseView_ViewRemoved() {
viewContainer = AdditionalSystemViewContainer(
mockContext,
- R.layout.desktop_mode_window_decor_handle_menu,
TASK_ID,
X,
Y,
WIDTH,
- HEIGHT
+ HEIGHT,
+ R.layout.desktop_mode_window_decor_handle_menu
)
verify(mockWindowManager).addView(eq(mockView), any())
viewContainer.releaseView()
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index ad963dd913cf..93118aeafaaf 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -40,6 +40,7 @@
namespace android {
namespace uirenderer {
+std::mutex TestUtils::sMutex;
std::unordered_map<int, TestUtils::CallCounts> TestUtils::sMockFunctorCounts{};
SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 0ede902b1b95..8ab2b16601c3 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -305,22 +305,26 @@ public:
.onSync =
[](int functor, void* client_data, const WebViewSyncData& data) {
expectOnRenderThread("onSync");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].sync++;
},
.onContextDestroyed =
[](int functor, void* client_data) {
expectOnRenderThread("onContextDestroyed");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].contextDestroyed++;
},
.onDestroyed =
[](int functor, void* client_data) {
expectOnRenderThread("onDestroyed");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].destroyed++;
},
.removeOverlays =
[](int functor, void* data,
void (*mergeTransaction)(ASurfaceTransaction*)) {
expectOnRenderThread("removeOverlays");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].removeOverlays++;
},
};
@@ -329,6 +333,7 @@ public:
callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params,
const WebViewOverlayData& overlay_params) {
expectOnRenderThread("draw");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].glesDraw++;
};
break;
@@ -336,15 +341,18 @@ public:
callbacks.vk.initialize = [](int functor, void* data,
const VkFunctorInitParams& params) {
expectOnRenderThread("initialize");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].vkInitialize++;
};
callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
const WebViewOverlayData& overlayParams) {
expectOnRenderThread("draw");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].vkDraw++;
};
callbacks.vk.postDraw = [](int functor, void* data) {
expectOnRenderThread("postDraw");
+ std::scoped_lock lock(sMutex);
sMockFunctorCounts[functor].vkPostDraw++;
};
break;
@@ -352,11 +360,16 @@ public:
return callbacks;
}
- static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
+ static CallCounts copyCountsForFunctor(int functor) {
+ std::scoped_lock lock(sMutex);
+ return sMockFunctorCounts[functor];
+ }
static SkFont defaultFont();
private:
+ // guards sMockFunctorCounts
+ static std::mutex sMutex;
static std::unordered_map<int, CallCounts> sMockFunctorCounts;
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index e727ea899098..690a60a470bc 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -239,19 +239,21 @@ TEST(RenderNode, releasedCallback) {
TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) {
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
});
- auto& counts = TestUtils::countsForFunctor(functor);
+ auto counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(1, counts.sync);
EXPECT_EQ(0, counts.destroyed);
TestUtils::recordNode(*node, [&](Canvas& canvas) {
canvas.drawWebViewFunctor(functor);
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(1, counts.sync);
EXPECT_EQ(0, counts.destroyed);
TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) {
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(2, counts.sync);
EXPECT_EQ(0, counts.destroyed);
@@ -265,6 +267,7 @@ TEST(RenderNode, releasedCallback) {
});
// Fence on any remaining post'd work
TestUtils::runOnRenderThreadUnmanaged([] (RenderThread&) {});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(2, counts.sync);
EXPECT_EQ(1, counts.destroyed);
}
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 064d42ec8941..26b47290d149 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -101,7 +101,7 @@ TEST(SkiaDisplayList, syncContexts) {
SkCanvas dummyCanvas;
int functor1 = TestUtils::createMockFunctor();
- auto& counts = TestUtils::countsForFunctor(functor1);
+ auto counts = TestUtils::copyCountsForFunctor(functor1);
skiaDL.mChildFunctors.push_back(
skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
WebViewFunctor_release(functor1);
@@ -118,6 +118,7 @@ TEST(SkiaDisplayList, syncContexts) {
});
});
+ counts = TestUtils::copyCountsForFunctor(functor1);
EXPECT_EQ(counts.sync, 1);
EXPECT_EQ(counts.destroyed, 0);
EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds);
@@ -126,6 +127,7 @@ TEST(SkiaDisplayList, syncContexts) {
TestUtils::runOnRenderThread([](auto&) {
// Fence
});
+ counts = TestUtils::copyCountsForFunctor(functor1);
EXPECT_EQ(counts.destroyed, 1);
}
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index 5e8f13d261c7..09ce98a2e53d 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -40,7 +40,7 @@ TEST(WebViewFunctor, createDestroyGLES) {
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
// Empty, don't care
});
- auto& counts = TestUtils::countsForFunctor(functor);
+ auto counts = TestUtils::copyCountsForFunctor(functor);
// We never initialized, so contextDestroyed == 0
EXPECT_EQ(0, counts.contextDestroyed);
EXPECT_EQ(1, counts.destroyed);
@@ -59,7 +59,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) {
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
// fence
});
- auto& counts = TestUtils::countsForFunctor(functor);
+ auto counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(0, counts.sync);
EXPECT_EQ(0, counts.contextDestroyed);
EXPECT_EQ(0, counts.destroyed);
@@ -69,6 +69,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) {
handle->sync(syncData);
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(1, counts.sync);
TestUtils::runOnRenderThreadUnmanaged([&](auto&) {
@@ -76,6 +77,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) {
handle->sync(syncData);
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(2, counts.sync);
handle.clear();
@@ -84,6 +86,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) {
// fence
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(2, counts.sync);
EXPECT_EQ(0, counts.contextDestroyed);
EXPECT_EQ(1, counts.destroyed);
@@ -98,7 +101,6 @@ TEST(WebViewFunctor, createSyncDrawGLES) {
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
WebViewFunctor_release(functor);
- auto& counts = TestUtils::countsForFunctor(functor);
for (int i = 0; i < 5; i++) {
TestUtils::runOnRenderThreadUnmanaged([&](auto&) {
WebViewSyncData syncData;
@@ -112,6 +114,7 @@ TEST(WebViewFunctor, createSyncDrawGLES) {
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
// fence
});
+ auto counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(5, counts.sync);
EXPECT_EQ(10, counts.glesDraw);
EXPECT_EQ(1, counts.contextDestroyed);
@@ -127,13 +130,13 @@ TEST(WebViewFunctor, contextDestroyedGLES) {
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
WebViewFunctor_release(functor);
- auto& counts = TestUtils::countsForFunctor(functor);
TestUtils::runOnRenderThreadUnmanaged([&](auto&) {
WebViewSyncData syncData;
handle->sync(syncData);
DrawGlInfo drawInfo;
handle->drawGl(drawInfo);
});
+ auto counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(1, counts.sync);
EXPECT_EQ(1, counts.glesDraw);
EXPECT_EQ(0, counts.contextDestroyed);
@@ -141,6 +144,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) {
TestUtils::runOnRenderThreadUnmanaged([](auto& rt) {
rt.destroyRenderingContext();
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(1, counts.sync);
EXPECT_EQ(1, counts.glesDraw);
EXPECT_EQ(1, counts.contextDestroyed);
@@ -151,6 +155,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) {
DrawGlInfo drawInfo;
handle->drawGl(drawInfo);
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(2, counts.sync);
EXPECT_EQ(2, counts.glesDraw);
EXPECT_EQ(1, counts.contextDestroyed);
@@ -159,6 +164,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) {
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
// fence
});
+ counts = TestUtils::copyCountsForFunctor(functor);
EXPECT_EQ(2, counts.sync);
EXPECT_EQ(2, counts.glesDraw);
EXPECT_EQ(2, counts.contextDestroyed);
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index acfe473c260c..0edaaefbd75d 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -2,6 +2,20 @@ package: "android.location.flags"
container: "system"
flag {
+ name: "keep_gnss_stationary_throttling"
+ namespace: "location"
+ description: "Keeps stationary throttling for the GNSS provider even if the disable_stationary_throttling flag is true."
+ bug: "354000147"
+}
+
+flag {
+ name: "disable_stationary_throttling"
+ namespace: "location"
+ description: "Disables stationary throttling for all providers"
+ bug: "354000147"
+}
+
+flag {
name: "new_geocoder"
namespace: "location"
description: "Flag for new Geocoder APIs"
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2d0e7abbe890..a255f730b0f3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2651,4 +2651,11 @@ public class AudioSystem
* @hide
*/
public static native boolean isBluetoothVariableLatencyEnabled();
+
+ /**
+ * Register a native listener for system property sysprop
+ * @param callback the listener which fires when the property changes
+ * @hide
+ */
+ public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 999f40e53952..1c5049e891e9 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -23,6 +23,7 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Build;
@@ -140,6 +141,7 @@ public final class MediaProjection {
/**
* @hide
*/
+ @Nullable
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
@@ -192,6 +194,11 @@ public final class MediaProjection {
* <li>If attempting to create a new virtual display
* associated with this MediaProjection instance after it has
* been stopped by invoking {@link #stop()}.
+ * <li>If attempting to create a new virtual display
+ * associated with this MediaProjection instance after a
+ * {@link MediaProjection.Callback#onStop()} callback has been
+ * received due to the user or the system stopping the
+ * MediaProjection session.
* <li>If the target SDK is {@link
* android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up,
* and if this instance has already taken a recording through
@@ -208,12 +215,17 @@ public final class MediaProjection {
* {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}.
* Instead, recording doesn't begin until the user re-grants
* consent in the dialog.
+ * @return The created {@link VirtualDisplay}, or {@code null} if no {@link VirtualDisplay}
+ * could be created.
* @see VirtualDisplay
* @see VirtualDisplay.Callback
*/
+ @SuppressWarnings("RequiresPermission")
+ @Nullable
public VirtualDisplay createVirtualDisplay(@NonNull String name,
- int width, int height, int dpi, int flags, @Nullable Surface surface,
- @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+ int width, int height, int dpi, @VirtualDisplayFlag int flags,
+ @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback,
+ @Nullable Handler handler) {
if (shouldMediaProjectionRequireCallback()) {
if (mCallbacks.isEmpty()) {
final IllegalStateException e = new IllegalStateException(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 88770d487a83..186b69b4107b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -912,8 +912,10 @@ class InstallRepository(private val context: Context) {
"message: $message"
)
}
+
+ val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+
if (statusCode == PackageInstaller.STATUS_SUCCESS) {
- val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
val resultIntent = if (shouldReturnResult) {
Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
} else {
@@ -922,12 +924,34 @@ class InstallRepository(private val context: Context) {
}
_installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
} else {
- if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
+ // TODO (b/346655018): Use INSTALL_FAILED_ABORTED legacyCode in the condition
+ // statusCode can be STATUS_FAILURE_ABORTED if:
+ // 1. GPP blocks an install.
+ // 2. User denies ownership update explicitly.
+ // InstallFailed dialog must not be shown only when the user denies ownership update. We
+ // must show this dialog for all other install failures.
+
+ val userDenied =
+ statusCode == PackageInstaller.STATUS_FAILURE_ABORTED &&
+ legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT &&
+ legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE
+
+ if (shouldReturnResult) {
+ val resultIntent = Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
_installResult.setValue(
- InstallFailed(appSnippet, statusCode, legacyStatus, message)
+ InstallFailed(
+ legacyCode = legacyStatus,
+ statusCode = statusCode,
+ shouldReturnResult = true,
+ resultIntent = resultIntent
+ )
)
- } else {
+ } else if (userDenied) {
_installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
+ } else {
+ _installResult.setValue(
+ InstallFailed(appSnippet, legacyStatus, statusCode, message)
+ )
}
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index 5dd4d2905f47..8de8fbb3e688 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -19,6 +19,7 @@ package com.android.packageinstaller.v2.model
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.pm.PackageInstaller
import android.graphics.drawable.Drawable
sealed class InstallStage(val stageCode: Int) {
@@ -77,11 +78,10 @@ data class InstallSuccess(
val shouldReturnResult: Boolean = false,
/**
*
- * * If the caller is requesting a result back, this will hold the Intent with
- * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED] which is sent
- * back to the caller.
+ * * If the caller is requesting a result back, this will hold an Intent with
+ * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED].
*
- * * If the caller doesn't want the result back, this will hold the Intent that launches
+ * * If the caller doesn't want the result back, this will hold an Intent that launches
* the newly installed / updated app if a launchable activity exists.
*/
val resultIntent: Intent? = null,
@@ -95,17 +95,23 @@ data class InstallSuccess(
}
data class InstallFailed(
- private val appSnippet: PackageUtil.AppSnippet,
+ private val appSnippet: PackageUtil.AppSnippet? = null,
val legacyCode: Int,
val statusCode: Int,
- val message: String?,
+ val message: String? = null,
+ val shouldReturnResult: Boolean = false,
+ /**
+ * If the caller is requesting a result back, this will hold an Intent with
+ * [Intent.EXTRA_INSTALL_RESULT] set to the [PackageInstaller.EXTRA_LEGACY_STATUS].
+ */
+ val resultIntent: Intent? = null
) : InstallStage(STAGE_FAILED) {
val appIcon: Drawable?
- get() = appSnippet.icon
+ get() = appSnippet?.icon
val appLabel: String?
- get() = appSnippet.label as String?
+ get() = appSnippet?.label as String?
}
data class InstallAborted(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index 31b9ccbdb838..e2ab31662380 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -171,15 +171,20 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
val successIntent = success.resultIntent
setResult(Activity.RESULT_OK, successIntent, true)
} else {
- val successFragment = InstallSuccessFragment(success)
- showDialogInner(successFragment)
+ val successDialog = InstallSuccessFragment(success)
+ showDialogInner(successDialog)
}
}
InstallStage.STAGE_FAILED -> {
val failed = installStage as InstallFailed
- val failedDialog = InstallFailedFragment(failed)
- showDialogInner(failedDialog)
+ if (failed.shouldReturnResult) {
+ val failureIntent = failed.resultIntent
+ setResult(Activity.RESULT_FIRST_USER, failureIntent, true)
+ } else {
+ val failureDialog = InstallFailedFragment(failed)
+ showDialogInner(failureDialog)
+ }
}
else -> {
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index d6345cec7c30..f36344aa4846 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-beta02"
+ extra["jetpackComposeVersion"] = "1.7.0-beta05"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index a842009a09bb..1cca73af9a42 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.5.0"
+agp = "8.5.1"
compose-compiler = "1.5.11"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip
index 77e6ad3f2117..9a97e4674448 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index e6441136f3d4..2c3521197d7c 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 91d2a3ab217d..9f29c77d55f6 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=gradle-8.8-bin.zip
+distributionUrl=gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew
index b740cf13397a..f5feea6d6b11 100755
--- a/packages/SettingsLib/Spa/gradlew
+++ b/packages/SettingsLib/Spa/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 6df0e9956f27..ac44a1be4cff 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -25,9 +25,6 @@ android_library {
use_resource_processor: true,
static_libs: [
"SettingsLibColor",
- "androidx.slice_slice-builders",
- "androidx.slice_slice-core",
- "androidx.slice_slice-view",
"androidx.compose.animation_animation",
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 9b8ecf773661..ce3d96e2d388 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -53,17 +53,14 @@ android {
dependencies {
api(project(":SettingsLibColor"))
- api("androidx.appcompat:appcompat:1.7.0-rc01")
- api("androidx.slice:slice-builders:1.1.0-alpha02")
- api("androidx.slice:slice-core:1.1.0-alpha02")
- api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.3.0-beta02")
+ api("androidx.appcompat:appcompat:1.7.0")
+ api("androidx.compose.material3:material3:1.3.0-beta04")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-beta02")
+ api("androidx.navigation:navigation-compose:2.8.0-beta05")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
@@ -97,10 +94,6 @@ tasks.register<JacocoReport>("coverageReport") {
// Excludes debug functions
"com/android/settingslib/spa/framework/compose/TimeMeasurer*",
-
- // Excludes slice demo presenter & provider
- "com/android/settingslib/spa/slice/presenter/Demo*",
- "com/android/settingslib/spa/slice/provider/Demo*",
)
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
index 085c3c6e026b..ddb571d1392f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
@@ -55,7 +55,6 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings
toPage = toPage,
// attributes
- // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately
isAllowSearch = isEnabled && isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
hasMutableStatus = hasMutableStatus,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 95c7d2371b3d..cc5351a3b81d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -57,8 +57,8 @@ interface SettingsPageProvider {
/**
* The API to indicate whether the page is enabled or not.
* During SPA page migration, one can use it to enable certain pages in one release.
- * When the page is disabled, all its related functionalities, such as browsing, search,
- * slice provider, are disabled as well.
+ * When the page is disabled, all its related functionalities, such as browsing and search,
+ * are disabled as well.
*/
fun isEnabled(arguments: Bundle?): Boolean = true
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
index d8c35a36d061..9e8ca0cd71a4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -25,7 +25,6 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
const val SESSION_UNKNOWN = "unknown"
const val SESSION_BROWSE = "browse"
const val SESSION_SEARCH = "search"
-const val SESSION_SLICE = "slice"
const val SESSION_EXTERNAL = "external"
const val KEY_DESTINATION = "spaActivityDestination"
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 4028b73a2c71..714f9519f378 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -18,9 +18,7 @@ package com.android.settingslib.mobile.dataservice;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.telephony.UiccCardInfo;
import android.telephony.UiccPortInfo;
-import android.telephony.UiccSlotInfo;
import android.telephony.UiccSlotMapping;
public class DataServiceUtils {
@@ -71,53 +69,9 @@ public class DataServiceUtils {
public static final String COLUMN_ID = "sudId";
/**
- * The name of the physical slot index column, see
- * {@link UiccSlotMapping#getPhysicalSlotIndex()}.
- */
- public static final String COLUMN_PHYSICAL_SLOT_INDEX = "physicalSlotIndex";
-
- /**
- * The name of the logical slot index column, see
- * {@link UiccSlotMapping#getLogicalSlotIndex()}.
- */
- public static final String COLUMN_LOGICAL_SLOT_INDEX = "logicalSlotIndex";
-
- /**
- * The name of the card ID column, see {@link UiccCardInfo#getCardId()}.
- */
- public static final String COLUMN_CARD_ID = "cardId";
-
- /**
- * The name of the eUICC state column, see {@link UiccCardInfo#isEuicc()}.
- */
- public static final String COLUMN_IS_EUICC = "isEuicc";
-
- /**
- * The name of the multiple enabled profiles supported state column, see
- * {@link UiccCardInfo#isMultipleEnabledProfilesSupported()}.
- */
- public static final String COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED =
- "isMultipleEnabledProfilesSupported";
-
- /**
- * The name of the card state column, see {@link UiccSlotInfo#getCardStateInfo()}.
- */
- public static final String COLUMN_CARD_STATE = "cardState";
-
- /**
- * The name of the removable state column, see {@link UiccSlotInfo#isRemovable()}.
- */
- public static final String COLUMN_IS_REMOVABLE = "isRemovable";
-
- /**
* The name of the active state column, see {@link UiccPortInfo#isActive()}.
*/
public static final String COLUMN_IS_ACTIVE = "isActive";
-
- /**
- * The name of the port index column, see {@link UiccPortInfo#getPortIndex()}.
- */
- public static final String COLUMN_PORT_INDEX = "portIndex";
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
index c92204fa1f39..5f7fa278082b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -19,14 +19,13 @@ package com.android.settingslib.mobile.dataservice;
import android.content.Context;
import android.util.Log;
-import java.util.List;
-import java.util.Objects;
-
import androidx.lifecycle.LiveData;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import java.util.List;
+import java.util.Objects;
@Database(entities = {SubscriptionInfoEntity.class, UiccInfoEntity.class,
MobileNetworkInfoEntity.class}, exportSchema = false, version = 1)
@@ -132,13 +131,6 @@ public abstract class MobileNetworkDatabase extends RoomDatabase {
}
/**
- * Query the UICC info by the subscription ID from the UiccInfoEntity table.
- */
- public LiveData<UiccInfoEntity> queryUiccInfoById(String id) {
- return mUiccInfoDao().queryUiccInfoById(id);
- }
-
- /**
* Delete the subscriptionInfo info by the subscription ID from the SubscriptionInfoEntity
* table.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
index 7e60421d0ab4..90e5189fdf1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
@@ -16,14 +16,14 @@
package com.android.settingslib.mobile.dataservice;
-import java.util.List;
-
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
+import java.util.List;
+
@Dao
public interface UiccInfoDao {
@@ -34,14 +34,6 @@ public interface UiccInfoDao {
+ DataServiceUtils.UiccInfoData.COLUMN_ID)
LiveData<List<UiccInfoEntity>> queryAllUiccInfos();
- @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
- + DataServiceUtils.UiccInfoData.COLUMN_ID + " = :subId")
- LiveData<UiccInfoEntity> queryUiccInfoById(String subId);
-
- @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
- + DataServiceUtils.UiccInfoData.COLUMN_IS_EUICC + " = :isEuicc")
- LiveData<List<UiccInfoEntity>> queryUiccInfosByEuicc(boolean isEuicc);
-
@Query("SELECT COUNT(*) FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME)
int count();
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
index 2ccf295007dc..0f80edf52d80 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
@@ -26,20 +26,9 @@ import androidx.room.PrimaryKey;
@Entity(tableName = DataServiceUtils.UiccInfoData.TABLE_NAME)
public class UiccInfoEntity {
- public UiccInfoEntity(@NonNull String subId, @NonNull String physicalSlotIndex,
- int logicalSlotIndex, int cardId, boolean isEuicc,
- boolean isMultipleEnabledProfilesSupported, int cardState, boolean isRemovable,
- boolean isActive, int portIndex) {
+ public UiccInfoEntity(@NonNull String subId, boolean isActive) {
this.subId = subId;
- this.physicalSlotIndex = physicalSlotIndex;
- this.logicalSlotIndex = logicalSlotIndex;
- this.cardId = cardId;
- this.isEuicc = isEuicc;
- this.isMultipleEnabledProfilesSupported = isMultipleEnabledProfilesSupported;
- this.cardState = cardState;
- this.isRemovable = isRemovable;
this.isActive = isActive;
- this.portIndex = portIndex;
}
@PrimaryKey
@@ -47,48 +36,14 @@ public class UiccInfoEntity {
@NonNull
public String subId;
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PHYSICAL_SLOT_INDEX)
- @NonNull
- public String physicalSlotIndex;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_LOGICAL_SLOT_INDEX)
- public int logicalSlotIndex;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_CARD_ID)
- public int cardId;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_EUICC)
- public boolean isEuicc;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED)
- public boolean isMultipleEnabledProfilesSupported;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_CARD_STATE)
- public int cardState;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_REMOVABLE)
- public boolean isRemovable;
-
@ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_ACTIVE)
public boolean isActive;
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PORT_INDEX)
- public int portIndex;
-
-
@Override
public int hashCode() {
int result = 17;
result = 31 * result + subId.hashCode();
- result = 31 * result + physicalSlotIndex.hashCode();
- result = 31 * result + logicalSlotIndex;
- result = 31 * result + cardId;
- result = 31 * result + Boolean.hashCode(isEuicc);
- result = 31 * result + Boolean.hashCode(isMultipleEnabledProfilesSupported);
- result = 31 * result + cardState;
- result = 31 * result + Boolean.hashCode(isRemovable);
result = 31 * result + Boolean.hashCode(isActive);
- result = 31 * result + portIndex;
return result;
}
@@ -102,40 +57,15 @@ public class UiccInfoEntity {
}
UiccInfoEntity info = (UiccInfoEntity) obj;
- return TextUtils.equals(subId, info.subId)
- && TextUtils.equals(physicalSlotIndex, info.physicalSlotIndex)
- && logicalSlotIndex == info.logicalSlotIndex
- && cardId == info.cardId
- && isEuicc == info.isEuicc
- && isMultipleEnabledProfilesSupported == info.isMultipleEnabledProfilesSupported
- && cardState == info.cardState
- && isRemovable == info.isRemovable
- && isActive == info.isActive
- && portIndex == info.portIndex;
+ return TextUtils.equals(subId, info.subId) && isActive == info.isActive;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(" {UiccInfoEntity(subId = ")
.append(subId)
- .append(", logicalSlotIndex = ")
- .append(physicalSlotIndex)
- .append(", logicalSlotIndex = ")
- .append(logicalSlotIndex)
- .append(", cardId = ")
- .append(cardId)
- .append(", isEuicc = ")
- .append(isEuicc)
- .append(", isMultipleEnabledProfilesSupported = ")
- .append(isMultipleEnabledProfilesSupported)
- .append(", cardState = ")
- .append(cardState)
- .append(", isRemovable = ")
- .append(isRemovable)
.append(", isActive = ")
.append(isActive)
- .append(", portIndex = ")
- .append(portIndex)
.append(")}");
return builder.toString();
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
index 1e75014d2017..3906749ae494 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
@@ -24,9 +24,9 @@ import android.os.UserHandle;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -109,7 +109,7 @@ public class InputMethodPreferenceTest {
final boolean systemIme,
final String name) {
return new InputMethodPreference(
- InstrumentationRegistry.getTargetContext(),
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
createInputMethodInfo(systemIme, name),
title,
true /* isAllowedByOrganization */,
@@ -119,7 +119,8 @@ public class InputMethodPreferenceTest {
private static InputMethodInfo createInputMethodInfo(
final boolean systemIme, final String name) {
- final Context targetContext = InstrumentationRegistry.getTargetContext();
+ final Context targetContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
final Locale systemLocale = targetContext
.getResources()
.getConfiguration()
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java
index f1c0beae0dc0..2c3478d6e235 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java
@@ -18,9 +18,9 @@ package com.android.settingslib.inputmethod;
import android.text.TextUtils;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -104,7 +104,7 @@ public class InputMethodSubtypePreferenceTest {
final Locale subtypeLocale = TextUtils.isEmpty(subtypeLanguageTag)
? null : Locale.forLanguageTag(subtypeLanguageTag);
return new InputMethodSubtypePreference(
- InstrumentationRegistry.getTargetContext(),
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
key,
subtypeName,
subtypeLocale,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5f236516785d..2b8b23e3dba8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -282,5 +282,6 @@ public class SecureSettings {
Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
Settings.Secure.MANDATORY_BIOMETRICS,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index c8da8afc25c3..cc5302bdd99a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -441,5 +441,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1));
+ VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ new InclusiveIntegerRangeValidator(0, 1));
}
}
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
index afcd8a9624c8..f2b5efa6a571 100644
--- a/packages/SystemUI/aconfig/communal.aconfig
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -8,12 +8,3 @@ flag {
bug: "304584416"
}
-flag {
- name: "enable_widget_picker_size_filter"
- namespace: "communal"
- description: "Enables passing a size filter to the widget picker"
- bug: "345482907"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 035e2fbf8e65..02e042327d66 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -988,6 +988,16 @@ flag {
}
flag {
+ name: "communal_scene_ktf_refactor"
+ namespace: "systemui"
+ description: "refactors the syncing mechanism between communal STL and KTF state."
+ bug: "327225415"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
@@ -1196,6 +1206,16 @@ flag {
}
flag {
+ namespace: "systemui"
+ name: "remove_update_listener_in_qs_icon_view_impl"
+ description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
+ bug: "327078684"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "sim_pin_race_condition_on_restart"
namespace: "systemui"
description: "The SIM PIN screen may be shown incorrectly on reboot"
@@ -1213,4 +1233,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "lockscreen_preview_renderer_create_on_main_thread"
+ namespace: "systemui"
+ description: "Force preview renderer to be created on the main thread"
+ bug: "343732179"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 1c02d3f7662b..68e968fdfb4e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1004,6 +1004,7 @@ private fun WidgetContent(
}
.thenIf(viewModel.isEditMode) {
Modifier.semantics {
+ onClick(clickActionLabel, null)
contentDescription = accessibilityLabel
val deleteAction =
CustomAccessibilityAction(removeWidgetActionLabel) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index 620892adc286..b4c1a2e85daf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -50,7 +50,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@@ -102,8 +101,6 @@ constructor(
val interactionSource = remember { MutableInteractionSource() }
val focusRequester = remember { FocusRequester() }
- val context = LocalContext.current
-
LaunchedEffect(Unit) {
// Adding a delay to ensure the animation completes before requesting focus
delay(250)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
index c34fb38f8558..c9938552a3d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -55,7 +55,7 @@ fun LockscreenLongPress(
modifier
.pointerInput(isEnabled) {
if (isEnabled) {
- detectLongPressGesture { viewModel.onLongPress() }
+ detectLongPressGesture { viewModel.onLongPress(isA11yAction = false) }
}
}
.pointerInput(Unit) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index d629eec1c45a..f8bd633d99a6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -35,7 +35,7 @@ import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.res.R
import com.android.systemui.util.animation.MeasurementInput
-private object MediaCarousel {
+object MediaCarousel {
object Elements {
internal val Content =
ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
index 039813390f1e..a22bc34381fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -25,7 +25,7 @@ import com.android.systemui.scene.shared.model.Scenes
/** [ElementScenePicker] implementation for the media carousel object. */
object MediaScenePicker : ElementScenePicker {
- private val shadeLockscreenFraction = 0.65f
+ const val SHADE_FRACTION = 0.66f
private val scenes =
setOf(
Scenes.Lockscreen,
@@ -44,7 +44,7 @@ object MediaScenePicker : ElementScenePicker {
return when {
// TODO: 352052894 - update with the actual scene picking
transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
- if (transition.progress < shadeLockscreenFraction) {
+ if (transition.progress < SHADE_FRACTION) {
Scenes.Lockscreen
} else {
Scenes.Shade
@@ -53,7 +53,7 @@ object MediaScenePicker : ElementScenePicker {
// TODO: 345467290 - update with the actual scene picking
transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
- if (transition.progress < 1f - shadeLockscreenFraction) {
+ if (transition.progress < 1f - SHADE_FRACTION) {
Scenes.Shade
} else {
Scenes.Lockscreen
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index df47cbab4b90..7d46c7570dac 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -25,6 +25,8 @@ import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.MediaScenePicker
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
@@ -59,10 +61,13 @@ fun TransitionBuilder.toShadeTransition(
fade(QuickSettings.Elements.SplitShadeQuickSettings)
fade(QuickSettings.Elements.FooterActions)
}
- translate(
- QuickSettings.Elements.QuickQuickSettings,
- y = -ShadeHeader.Dimensions.CollapsedHeight * .66f
- )
+
+ val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION
+ val qsExpansionDiff =
+ ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
+
+ translate(QuickSettings.Elements.QuickQuickSettings, y = -qsTranslation)
+ translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index ea740a8d8de4..82c85d1fa38b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.launch
* the currently running transition, if there is one.
*/
internal fun CoroutineScope.animateToScene(
- layoutState: BaseSceneTransitionLayoutState,
+ layoutState: MutableSceneTransitionLayoutStateImpl,
target: SceneKey,
transitionKey: TransitionKey?,
): TransitionState.Transition? {
@@ -154,7 +154,7 @@ internal fun CoroutineScope.animateToScene(
}
private fun CoroutineScope.animate(
- layoutState: BaseSceneTransitionLayoutState,
+ layoutState: MutableSceneTransitionLayoutStateImpl,
targetScene: SceneKey,
transitionKey: TransitionKey?,
isInitiatedByUserInput: Boolean,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 78ba7defe77c..5b328b8cdd9c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -52,11 +52,19 @@ internal interface DraggableHandler {
* and [onStop] methods.
*/
internal interface DragController {
- /** Drag the current scene by [delta] pixels. */
- fun onDrag(delta: Float)
+ /**
+ * Drag the current scene by [delta] pixels.
+ *
+ * @return the consumed [delta]
+ */
+ fun onDrag(delta: Float): Float
- /** Starts a transition to a target scene. */
- fun onStop(velocity: Float, canChangeScene: Boolean)
+ /**
+ * Starts a transition to a target scene.
+ *
+ * @return the consumed [velocity]
+ */
+ fun onStop(velocity: Float, canChangeScene: Boolean): Float
}
internal class DraggableHandlerImpl(
@@ -272,8 +280,10 @@ private class DragControllerImpl(
*
* @return the consumed delta
*/
- override fun onDrag(delta: Float) {
- if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return
+ override fun onDrag(delta: Float): Float {
+ if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) {
+ return 0f
+ }
swipeTransition.dragOffset += delta
val (fromScene, acceleratedOffset) =
@@ -289,7 +299,7 @@ private class DragControllerImpl(
if (result == null) {
onStop(velocity = delta, canChangeScene = true)
- return
+ return 0f
}
if (
@@ -314,6 +324,8 @@ private class DragControllerImpl(
updateTransition(swipeTransition)
}
+
+ return delta
}
/**
@@ -351,10 +363,10 @@ private class DragControllerImpl(
}
}
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
+ override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
// The state was changed since the drag started; don't do anything.
if (!isDrivingTransition || swipeTransition.isFinishing) {
- return
+ return 0f
}
// Important: Make sure that all the code here references the current transition when
@@ -370,9 +382,6 @@ private class DragControllerImpl(
// immediately go back B => A.
if (targetScene != swipeTransition._currentScene) {
swipeTransition._currentScene = targetScene
- with(draggableHandler.layoutImpl.state) {
- draggableHandler.coroutineScope.onChangeScene(targetScene.key)
- }
}
swipeTransition.animateOffset(
@@ -443,7 +452,7 @@ private class DragControllerImpl(
if (result == null) {
// We will not animate
swipeTransition.snapToScene(fromScene.key)
- return
+ return 0f
}
val newSwipeTransition =
@@ -465,6 +474,9 @@ private class DragControllerImpl(
animateTo(targetScene = fromScene, targetOffset = 0f)
}
}
+
+ // The onStop animation consumes any remaining velocity.
+ return velocity
}
/**
@@ -512,7 +524,7 @@ private class DragControllerImpl(
}
private fun SwipeTransition(
- layoutState: BaseSceneTransitionLayoutState,
+ layoutState: MutableSceneTransitionLayoutStateImpl,
coroutineScope: CoroutineScope,
fromScene: Scene,
result: UserActionResult,
@@ -567,7 +579,7 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
private class SwipeTransition(
val layoutImpl: SceneTransitionLayoutImpl,
- val layoutState: BaseSceneTransitionLayoutState,
+ val layoutState: MutableSceneTransitionLayoutStateImpl,
val coroutineScope: CoroutineScope,
override val key: TransitionKey?,
val _fromScene: Scene,
@@ -1084,17 +1096,13 @@ internal class NestedScrollHandlerImpl(
// TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
// initiated in a nested child.
controller.onDrag(delta = offsetAvailable)
-
- offsetAvailable
},
onStop = { velocityAvailable ->
val controller = dragController ?: error("Should be called after onStart")
- controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
-
- dragController = null
- // The onDragStopped animation consumes any remaining velocity.
- velocityAvailable
+ controller
+ .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
+ .also { dragController = null }
},
)
}
@@ -1109,7 +1117,7 @@ internal class NestedScrollHandlerImpl(
internal const val OffsetVisibilityThreshold = 0.5f
private object NoOpDragController : DragController {
- override fun onDrag(delta: Float) {}
+ override fun onDrag(delta: Float) = 0f
- override fun onStop(velocity: Float, canChangeScene: Boolean) {}
+ override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index 734241e2faf6..d3e2a1cfbb3f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -35,7 +35,7 @@ import kotlinx.coroutines.launch
@Composable
internal fun PredictiveBackHandler(
- state: BaseSceneTransitionLayoutState,
+ state: MutableSceneTransitionLayoutStateImpl,
coroutineScope: CoroutineScope,
targetSceneForBack: SceneKey? = null,
) {
@@ -65,7 +65,7 @@ internal fun PredictiveBackHandler(
}
private class PredictiveBackTransition(
- val state: BaseSceneTransitionLayoutState,
+ val state: MutableSceneTransitionLayoutStateImpl,
val coroutineScope: CoroutineScope,
fromScene: SceneKey,
toScene: SceneKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 82275a9ac0a6..2fc4526b31f2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -514,7 +514,7 @@ internal fun SceneTransitionLayoutForTesting(
val coroutineScope = rememberCoroutineScope()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
- state = state as BaseSceneTransitionLayoutState,
+ state = state as MutableSceneTransitionLayoutStateImpl,
density = density,
layoutDirection = layoutDirection,
swipeSourceDetector = swipeSourceDetector,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 3e48c429ba7d..32db0b7cd9fe 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -44,7 +44,7 @@ internal typealias MovableElementContent = @Composable (@Composable () -> Unit)
@Stable
internal class SceneTransitionLayoutImpl(
- internal val state: BaseSceneTransitionLayoutState,
+ internal val state: MutableSceneTransitionLayoutStateImpl,
internal var density: Density,
internal var layoutDirection: LayoutDirection,
internal var swipeSourceDetector: SwipeSourceDetector,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 56c8752eb53c..48bc251f1f87 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -200,7 +200,7 @@ sealed interface TransitionState {
* transition.
*
* Important: These will be set exactly once, when this transition is
- * [started][BaseSceneTransitionLayoutState.startTransition].
+ * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
*/
internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
private var fromOverscrollSpec: OverscrollSpecImpl? = null
@@ -332,13 +332,16 @@ sealed interface TransitionState {
}
}
-internal abstract class BaseSceneTransitionLayoutState(
+/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
- protected var stateLinks: List<StateLink>,
+ override var transitions: SceneTransitions = transitions {},
+ internal val canChangeScene: (SceneKey) -> Boolean = { true },
+ private val stateLinks: List<StateLink> = emptyList(),
// TODO(b/290930950): Remove this flag.
- internal var enableInterruptions: Boolean,
-) : SceneTransitionLayoutState {
+ internal val enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
+) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
/**
@@ -374,17 +377,6 @@ internal abstract class BaseSceneTransitionLayoutState(
@VisibleForTesting
internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>()
- /** Whether we can transition to the given [scene]. */
- internal abstract fun canChangeScene(scene: SceneKey): Boolean
-
- /**
- * Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
- *
- * When this is called, the source of truth for the current scene should be changed so that
- * [transitionState] will animate and settle to [scene].
- */
- internal abstract fun CoroutineScope.onChangeScene(scene: SceneKey)
-
internal fun checkThread() {
val current = Thread.currentThread()
if (current !== creationThread) {
@@ -409,6 +401,20 @@ internal abstract class BaseSceneTransitionLayoutState(
return transition.isTransitioningBetween(scene, other)
}
+ override fun setTargetScene(
+ targetScene: SceneKey,
+ coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey?,
+ ): TransitionState.Transition? {
+ checkThread()
+
+ return coroutineScope.animateToScene(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ target = targetScene,
+ transitionKey = transitionKey,
+ )
+ }
+
/**
* Start a new [transition].
*
@@ -600,7 +606,7 @@ internal abstract class BaseSceneTransitionLayoutState(
}
}
- fun snapToScene(scene: SceneKey) {
+ override fun snapToScene(scene: SceneKey) {
checkThread()
// Force finish all transitions.
@@ -674,37 +680,6 @@ internal abstract class BaseSceneTransitionLayoutState(
}
}
-/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
-internal class MutableSceneTransitionLayoutStateImpl(
- initialScene: SceneKey,
- override var transitions: SceneTransitions = transitions {},
- private val canChangeScene: (SceneKey) -> Boolean = { true },
- stateLinks: List<StateLink> = emptyList(),
- enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-) :
- MutableSceneTransitionLayoutState,
- BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) {
- override fun setTargetScene(
- targetScene: SceneKey,
- coroutineScope: CoroutineScope,
- transitionKey: TransitionKey?,
- ): TransitionState.Transition? {
- checkThread()
-
- return coroutineScope.animateToScene(
- layoutState = this@MutableSceneTransitionLayoutStateImpl,
- target = targetScene,
- transitionKey = transitionKey,
- )
- }
-
- override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
-
- override fun CoroutineScope.onChangeScene(scene: SceneKey) {
- setTargetScene(scene, coroutineScope = this)
- }
-}
-
private const val TAG = "SceneTransitionLayoutState"
/** Whether support for interruptions in enabled by default. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index 6c299463f978..2018d6e37b57 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,7 +16,7 @@
package com.android.compose.animation.scene.transition.link
-import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.TransitionKey
@@ -25,7 +25,7 @@ import com.android.compose.animation.scene.TransitionState
/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
- internal val target = target as BaseSceneTransitionLayoutState
+ internal val target = target as MutableSceneTransitionLayoutStateImpl
/**
* Links two transitions (source and target) together.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 7a5a84e2c3f1..c8bbb149a042 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -212,7 +212,8 @@ class DraggableHandlerTest {
draggableHandler: DraggableHandler,
startedPosition: Offset = Offset.Zero,
overSlop: Float = 0f,
- pointersDown: Int = 1
+ pointersDown: Int = 1,
+ expectedConsumed: Boolean = true,
): DragController {
val dragController =
draggableHandler.onDragStarted(
@@ -222,17 +223,23 @@ class DraggableHandlerTest {
)
// MultiPointerDraggable will always call onDelta with the initial overSlop right after
- dragController.onDragDelta(pixels = overSlop)
+ dragController.onDragDelta(pixels = overSlop, expectedConsumed = expectedConsumed)
return dragController
}
- fun DragController.onDragDelta(pixels: Float) {
- onDrag(delta = pixels)
+ fun DragController.onDragDelta(pixels: Float, expectedConsumed: Boolean = true) {
+ val consumed = onDrag(delta = pixels)
+ assertThat(consumed).isEqualTo(if (expectedConsumed) pixels else 0f)
}
- fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) {
- onStop(velocity, canChangeScene)
+ fun DragController.onDragStopped(
+ velocity: Float,
+ canChangeScene: Boolean = true,
+ expectedConsumed: Boolean = true
+ ) {
+ val consumed = onStop(velocity, canChangeScene)
+ assertThat(consumed).isEqualTo(if (expectedConsumed) velocity else 0f)
}
fun NestedScrollConnection.scroll(
@@ -360,10 +367,18 @@ class DraggableHandlerTest {
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
+ onDragStarted(
+ horizontalDraggableHandler,
+ overSlop = up(fractionOfScreen = 0.3f),
+ expectedConsumed = false,
+ )
assertIdle(currentScene = SceneA)
- onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
+ onDragStarted(
+ horizontalDraggableHandler,
+ overSlop = down(fractionOfScreen = 0.3f),
+ expectedConsumed = false,
+ )
assertIdle(currentScene = SceneA)
}
@@ -489,19 +504,19 @@ class DraggableHandlerTest {
// start accelaratedScroll and scroll over to B -> null
val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
dragController2.onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false)
dragController2.onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@@ -845,7 +860,7 @@ class DraggableHandlerTest {
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- dragController.onDragStopped(-velocityThreshold)
+ dragController.onDragStopped(-velocityThreshold, expectedConsumed = false)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index ecafb170535d..b98400a70ea4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -49,6 +49,21 @@ import org.junit.runner.RunWith
class MultiPointerDraggableTest {
@get:Rule val rule = createComposeRule()
+ private class SimpleDragController(
+ val onDrag: () -> Unit,
+ val onStop: () -> Unit,
+ ) : DragController {
+ override fun onDrag(delta: Float): Float {
+ onDrag()
+ return delta
+ }
+
+ override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
+ onStop()
+ return velocity
+ }
+ }
+
@Test
fun cancellingPointerCallsOnDragStopped() {
val size = 200f
@@ -70,15 +85,10 @@ class MultiPointerDraggableTest {
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
started = true
- object : DragController {
- override fun onDrag(delta: Float) {
- dragged = true
- }
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
- stopped = true
- }
- }
+ SimpleDragController(
+ onDrag = { dragged = true },
+ onStop = { stopped = true },
+ )
},
)
)
@@ -142,15 +152,10 @@ class MultiPointerDraggableTest {
startDragImmediately = { true },
onDragStarted = { _, _, _ ->
started = true
- object : DragController {
- override fun onDrag(delta: Float) {
- dragged = true
- }
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
- stopped = true
- }
- }
+ SimpleDragController(
+ onDrag = { dragged = true },
+ onStop = { stopped = true },
+ )
},
)
.pointerInput(Unit) {
@@ -218,15 +223,10 @@ class MultiPointerDraggableTest {
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
started = true
- object : DragController {
- override fun onDrag(delta: Float) {
- dragged = true
- }
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
- stopped = true
- }
- }
+ SimpleDragController(
+ onDrag = { dragged = true },
+ onStop = { stopped = true },
+ )
},
)
) {
@@ -341,15 +341,10 @@ class MultiPointerDraggableTest {
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
started = true
- object : DragController {
- override fun onDrag(delta: Float) {
- dragged = true
- }
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
- stopped = true
- }
- }
+ SimpleDragController(
+ onDrag = { dragged = true },
+ onStop = { stopped = true },
+ )
},
)
) {
@@ -447,15 +442,10 @@ class MultiPointerDraggableTest {
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
verticalStarted = true
- object : DragController {
- override fun onDrag(delta: Float) {
- verticalDragged = true
- }
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
- verticalStopped = true
- }
- }
+ SimpleDragController(
+ onDrag = { verticalDragged = true },
+ onStop = { verticalStopped = true },
+ )
},
)
.multiPointerDraggable(
@@ -464,15 +454,10 @@ class MultiPointerDraggableTest {
startDragImmediately = { false },
onDragStarted = { _, _, _ ->
horizontalStarted = true
- object : DragController {
- override fun onDrag(delta: Float) {
- horizontalDragged = true
- }
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {
- horizontalStopped = true
- }
- }
+ SimpleDragController(
+ onDrag = { horizontalDragged = true },
+ onStop = { horizontalStopped = true },
+ )
},
)
)
@@ -567,11 +552,10 @@ class MultiPointerDraggableTest {
},
onDragStarted = { _, _, _ ->
started = true
- object : DragController {
- override fun onDrag(delta: Float) {}
-
- override fun onStop(velocity: Float, canChangeScene: Boolean) {}
- }
+ SimpleDragController(
+ onDrag = { /* do nothing */ },
+ onStop = { /* do nothing */ },
+ )
},
)
) {}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 41bf630e9ae4..52cceecadbf9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -153,7 +153,7 @@ class SceneTransitionLayoutStateTest {
sourceTo: SceneKey? = SceneB,
targetFrom: SceneKey? = SceneC,
targetTo: SceneKey = SceneD
- ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+ ): Pair<MutableSceneTransitionLayoutStateImpl, MutableSceneTransitionLayoutStateImpl> {
val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
val link =
listOf(
@@ -164,8 +164,8 @@ class SceneTransitionLayoutStateTest {
)
val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
return Pair(
- parentState as BaseSceneTransitionLayoutState,
- childState as BaseSceneTransitionLayoutState
+ parentState as MutableSceneTransitionLayoutStateImpl,
+ childState as MutableSceneTransitionLayoutStateImpl
)
}
@@ -187,7 +187,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun linkedTransition_transitiveLink() {
val parentParentState =
- MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+ MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl
val parentLink =
listOf(
StateLink(
@@ -197,7 +197,7 @@ class SceneTransitionLayoutStateTest {
)
val parentState =
MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
- as BaseSceneTransitionLayoutState
+ as MutableSceneTransitionLayoutStateImpl
val link =
listOf(
StateLink(
@@ -207,7 +207,7 @@ class SceneTransitionLayoutStateTest {
)
val childState =
MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
- as BaseSceneTransitionLayoutState
+ as MutableSceneTransitionLayoutStateImpl
val childTransition = transition(SceneA, SceneB)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 5cf4f16aed62..7fd9ce20ab92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -146,6 +146,14 @@ class ShadeTouchHandlerTest : SysuiTestCase() {
verify(mShadeViewController, never()).handleExternalTouch(any())
}
+ @Test
+ fun testCancelMotionEvent_popsTouchSession() {
+ swipe(Direction.DOWN)
+ val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
+ mInputListenerCaptor.lastValue.onInputEvent(event)
+ verify(mTouchSession).pop()
+ }
+
/**
* Simulates a swipe in the given direction and returns true if the touch was intercepted by the
* touch handler's gesture listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index a8bdc7c632d2..1f5e30ca4a0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -88,24 +88,6 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isDisclaimerDismissed_byDefault_isFalse() =
- testScope.runTest {
- val isDisclaimerDismissed by
- collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER))
- assertThat(isDisclaimerDismissed).isFalse()
- }
-
- @Test
- fun isDisclaimerDismissed_onSet_isTrue() =
- testScope.runTest {
- val isDisclaimerDismissed by
- collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER))
-
- underTest.setDisclaimerDismissed(MAIN_USER)
- assertThat(isDisclaimerDismissed).isTrue()
- }
-
- @Test
fun getSharedPreferences_whenFileRestored() =
testScope.runTest {
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 5cdbe9ce5856..9539c0492056 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -84,6 +84,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -1059,6 +1060,25 @@ class CommunalInteractorTest : SysuiTestCase() {
)
}
+ @Test
+ fun dismissDisclaimerSetsDismissedFlag() =
+ testScope.runTest {
+ val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+ assertThat(disclaimerDismissed).isFalse()
+ underTest.setDisclaimerDismissed()
+ assertThat(disclaimerDismissed).isTrue()
+ }
+
+ @Test
+ fun dismissDisclaimerTimeoutResetsDismissedFlag() =
+ testScope.runTest {
+ val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+ underTest.setDisclaimerDismissed()
+ assertThat(disclaimerDismissed).isTrue()
+ advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+ assertThat(disclaimerDismissed).isFalse()
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
index 7b79d2817478..9a92f76f90c6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
@@ -74,40 +74,6 @@ class CommunalPrefsInteractorTest : SysuiTestCase() {
assertThat(isCtaDismissed).isFalse()
}
- @Test
- fun setDisclaimerDismissed_currentUser() =
- testScope.runTest {
- setSelectedUser(MAIN_USER)
- val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
-
- assertThat(isDisclaimerDismissed).isFalse()
- underTest.setDisclaimerDismissed(MAIN_USER)
- assertThat(isDisclaimerDismissed).isTrue()
- }
-
- @Test
- fun setDisclaimerDismissed_anotherUser() =
- testScope.runTest {
- setSelectedUser(MAIN_USER)
- val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
-
- assertThat(isDisclaimerDismissed).isFalse()
- underTest.setDisclaimerDismissed(SECONDARY_USER)
- assertThat(isDisclaimerDismissed).isFalse()
- }
-
- @Test
- fun isDisclaimerDismissed_userSwitch() =
- testScope.runTest {
- setSelectedUser(MAIN_USER)
- underTest.setDisclaimerDismissed(MAIN_USER)
- val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
-
- assertThat(isDisclaimerDismissed).isTrue()
- setSelectedUser(SECONDARY_USER)
- assertThat(isDisclaimerDismissed).isFalse()
- }
-
private suspend fun setSelectedUser(user: UserInfo) {
with(kosmos.fakeUserRepository) {
setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
new file mode 100644
index 000000000000..f7f70c154ce6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2024 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.systemui.communal.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMMUNAL_HUB, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+@DisableSceneContainer
+class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository }
+ private val testScope = kosmos.testScope
+
+ private val underTest by lazy { kosmos.communalSceneTransitionInteractor }
+ private val keyguardTransitionRepository by lazy { kosmos.realKeyguardTransitionRepository }
+
+ private val ownerName = CommunalSceneTransitionInteractor::class.java.simpleName
+ private val progress = MutableSharedFlow<Float>()
+
+ private val sceneTransitions =
+ MutableStateFlow<ObservableTransitionState>(Idle(CommunalScenes.Blank))
+
+ private val blankToHub =
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Blank,
+ toScene = CommunalScenes.Communal,
+ currentScene = flowOf(CommunalScenes.Blank),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ private val hubToBlank =
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Communal,
+ toScene = CommunalScenes.Blank,
+ currentScene = flowOf(CommunalScenes.Communal),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ @Before
+ fun setup() {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ underTest.start()
+ kosmos.communalSceneRepository.setTransitionState(sceneTransitions)
+ testScope.launch { keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN) }
+ }
+
+ /** Transition from blank to glanceable hub. This is the default case. */
+ @Test
+ fun transition_from_blank_end_in_hub() =
+ testScope.runTest {
+ sceneTransitions.value = blankToHub
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(1f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = RUNNING,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = Idle(CommunalScenes.Communal)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ /** Transition from hub to lockscreen. */
+ @Test
+ fun transition_from_hub_end_in_lockscreen() =
+ testScope.runTest {
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ /** Transition from hub to dream. */
+ @Test
+ fun transition_from_hub_end_in_dream() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ runCurrent()
+
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = DREAMING,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = DREAMING,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = DREAMING,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ /** Transition from blank to hub, then settle back in blank. */
+ @Test
+ fun transition_from_blank_end_in_blank() =
+ testScope.runTest {
+ sceneTransitions.value = blankToHub
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+ val allSteps by collectValues(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ val numToDrop = allSteps.size
+ // Settle back in blank
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+ // Assert that KTF reversed transition back to lockscreen.
+ assertThat(allSteps.drop(numToDrop))
+ .containsExactly(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = CANCELED,
+ value = 0.4f,
+ ownerName = ownerName,
+ ),
+ // Transition back to lockscreen
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0.6f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun transition_to_occluded_with_changed_scene_respected_just_once() =
+ testScope.runTest {
+ underTest.onSceneAboutToChange(CommunalScenes.Blank, OCCLUDED)
+ runCurrent()
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = blankToHub
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = OCCLUDED,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = hubToBlank
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ @Test
+ fun transition_from_blank_interrupted() =
+ testScope.runTest {
+ sceneTransitions.value = blankToHub
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+ val allSteps by collectValues(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ val numToDrop = allSteps.size
+ // Transition back from hub to blank, interrupting
+ // the current transition.
+ sceneTransitions.value = hubToBlank
+
+ assertThat(allSteps.drop(numToDrop))
+ .containsExactly(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ value = 1f,
+ transitionState = FINISHED,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ value = 0f,
+ transitionState = STARTED,
+ ownerName = ownerName,
+ ),
+ )
+ .inOrder()
+
+ progress.emit(0.1f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = RUNNING,
+ value = 0.1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ /**
+ * Blank -> Hub transition interrupted by a new Blank -> Hub transition. KTF state should not be
+ * updated in this case.
+ */
+ @Test
+ fun transition_to_hub_duplicate_does_not_change_ktf() =
+ testScope.runTest {
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Blank,
+ toScene = CommunalScenes.Communal,
+ currentScene = flowOf(CommunalScenes.Blank),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+ val allSteps by collectValues(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ val sizeBefore = allSteps.size
+ val newProgress = MutableSharedFlow<Float>()
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Blank,
+ toScene = CommunalScenes.Communal,
+ currentScene = flowOf(CommunalScenes.Blank),
+ progress = newProgress,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+
+ // No new KTF steps emitted as a result of the new transition.
+ assertThat(allSteps).hasSize(sizeBefore)
+
+ // Progress is now tracked by the new flow.
+ newProgress.emit(0.1f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ transitionState = RUNNING,
+ value = 0.1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ /**
+ * STL: Hub -> Blank, then interrupt in KTF LS -> OCCLUDED, then STL still finishes in Blank.
+ * After a KTF transition is started (GLANCEABLE_HUB -> LOCKSCREEN) KTF immediately considers
+ * the active scene to be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active
+ * and may start a new transition LOCKSCREEN -> *. Here we test LOCKSCREEN -> OCCLUDED.
+ *
+ * KTF is allowed to already start and play the other transition, while the STL transition may
+ * finish later (gesture completes much later). When we eventually settle the STL transition in
+ * Blank we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for
+ * this scenario the settle can be ignored.
+ */
+ @Test
+ fun transition_to_blank_interrupted_by_ktf_transition_then_finish_in_blank() =
+ testScope.runTest {
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ // Start another transition externally while our scene
+ // transition is happening.
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = "external",
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = "external",
+ )
+ )
+
+ // Scene progress should not affect KTF transition anymore
+ progress.emit(0.7f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = "external",
+ )
+ )
+
+ // Scene transition still finishes but should not impact KTF transition
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = "external",
+ )
+ )
+ }
+
+ /**
+ * STL: Hub -> Blank, then interrupt in KTF LS -> OCCLUDED, then STL finishes back in Hub.
+ *
+ * This is similar to the previous scenario but the gesture may have been interrupted by any
+ * other transition. KTF needs to immediately finish in GLANCEABLE_HUB (there is a jump cut).
+ */
+ @Test
+ fun transition_to_blank_interrupted_by_ktf_transition_then_finish_in_hub() =
+ testScope.runTest {
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ // Start another transition externally while our scene
+ // transition is happening.
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = "external",
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = "external",
+ )
+ )
+
+ // Scene progress should not affect KTF transition anymore
+ progress.emit(0.7f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = "external",
+ )
+ )
+
+ // We land back in communal.
+ sceneTransitions.value = Idle(CommunalScenes.Communal)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = OCCLUDED,
+ to = GLANCEABLE_HUB,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index b138fb3b779a..f8906adc33d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -65,6 +65,7 @@ import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -352,6 +353,21 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
+ fun showDisclaimer_trueWhenTimeout() =
+ testScope.runTest {
+ underTest.setEditModeState(EditModeState.SHOWING)
+ kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+
+ val showDisclaimer by collectLastValue(underTest.showDisclaimer)
+
+ assertThat(showDisclaimer).isTrue()
+ underTest.onDisclaimerDismissed()
+ assertThat(showDisclaimer).isFalse()
+ advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+ assertThat(showDisclaimer).isTrue()
+ }
+
+ @Test
fun scrollPosition_persistedOnEditCleanup() {
val index = 2
val offset = 30
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
index ac50db483301..e36fd75445e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -14,17 +14,20 @@
* limitations under the License.
*/
+package com.android.systemui.communal.widgets
+
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -34,6 +37,7 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
@@ -66,7 +70,7 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
}
@Test
- fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() {
+ fun animationCancelled_launchingWidgetStateIsCleared() {
with(kosmos) {
testScope.runTest {
val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
@@ -81,9 +85,12 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
assertTrue(launching!!)
verify(controller).onIntentStarted(willAnimate = true)
+ underTest.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ assertTrue(launching!!)
+ verify(controller).onTransitionAnimationStart(isExpandingFullyAbove = true)
+
underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true)
assertFalse(launching!!)
- Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true)
}
}
@@ -105,6 +112,12 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
assertTrue(launching!!)
verify(controller).onIntentStarted(willAnimate = true)
+ underTest.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ assertTrue(launching!!)
+ verify(controller).onTransitionAnimationStart(isExpandingFullyAbove = true)
+
+ testScope.advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration)
+
underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true)
assertFalse(launching!!)
Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 73ef77540398..88ba0411b414 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -84,7 +84,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
verify(mockAnimator, atLeastOnce()).addListener(captor.capture())
captor.allValues.forEach { it.onAnimationEnd(mockAnimator) }
- verify(stateController).setExitAnimationsRunning(false)
+ verify(stateController, times(2)).setExitAnimationsRunning(false)
}
@Test
@@ -154,4 +154,10 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
}
)
}
+
+ @Test
+ fun testCancelAnimations_clearsExitAnimationsRunning() {
+ controller.cancelAnimations()
+ verify(stateController).setExitAnimationsRunning(false)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
new file mode 100644
index 000000000000..ee51e37faba7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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.systemui.education.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyboardTouchpadEduStatsInteractor
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCount() =
+ testScope.runTest {
+ val model by
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
+ )
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK_GESTURE)
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataAddedOnUpdateShortcutTriggerTime() =
+ testScope.runTest {
+ val model by
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
+ )
+ assertThat(model?.lastShortcutTriggeredTime).isNull()
+ underTest.updateShortcutTriggerTime(BACK_GESTURE)
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 693fcdabea58..18839e6acb96 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -22,6 +22,7 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.testScope
@@ -52,6 +53,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
private val vibratorHelper = kosmos.vibratorHelper
private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
@Mock private lateinit var callback: QSLongPressEffect.Callback
+ @Mock private lateinit var controller: ActivityTransitionAnimator.Controller
private val effectDuration = 400
private val lowTickDuration = 12
@@ -218,8 +220,9 @@ class QSLongPressEffectTest : SysuiTestCase() {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the effect ends in the idle state.
+ // THEN the effect ends in the idle state and the reversed callback is used.
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ verify(callback, times(1)).onEffectFinishedReversing()
}
@Test
@@ -348,6 +351,23 @@ class QSLongPressEffectTest : SysuiTestCase() {
assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
}
+ @Test
+ fun onLongClickTransitionCancelled_whileInLongClickState_reversesEffect() =
+ testWhileInState(QSLongPressEffect.State.LONG_CLICKED) {
+ // GIVEN a transition controller delegate
+ val delegate = longPressEffect.createTransitionControllerDelegate(controller)
+
+ // WHEN the activity launch animation is cancelled
+ val newOccludedState = false
+ delegate.onTransitionAnimationCancelled(newOccludedState)
+
+ // THEN the effect reverses and ends in RUNNING_BACKWARDS_FROM_CANCEL
+ assertThat(longPressEffect.state)
+ .isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL)
+ verify(callback, times(1)).onReverseAnimator(false)
+ verify(controller).onTransitionAnimationCancelled(newOccludedState)
+ }
+
private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 96b4b4325408..2b2c121fb79a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -171,14 +171,15 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
}
@Test
- fun longPressed_openWppDirectlyEnabled_doesNotShowMenu_opensSettings() =
+ fun longPressed_isA11yAction_doesNotShowMenu_opensSettings() =
testScope.runTest {
- createUnderTest(isOpenWppDirectlyEnabled = true)
+ createUnderTest()
val isMenuVisible by collectLastValue(underTest.isMenuVisible)
val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
+ val isA11yAction = true
runCurrent()
- underTest.onLongPress()
+ underTest.onLongPress(isA11yAction)
assertThat(isMenuVisible).isFalse()
assertThat(shouldOpenSettings).isTrue()
@@ -284,7 +285,6 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
private suspend fun createUnderTest(
isLongPressFeatureEnabled: Boolean = true,
isRevampedWppFeatureEnabled: Boolean = true,
- isOpenWppDirectlyEnabled: Boolean = false,
) {
// This needs to be re-created for each test outside of kosmos since the flag values are
// read during initialization to set up flows. Maybe there is a better way to handle that.
@@ -298,7 +298,6 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
featureFlags =
kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
- set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
},
broadcastDispatcher = fakeBroadcastDispatcher,
accessibilityManager = kosmos.accessibilityManagerWrapper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index fc3b35d637f7..42cd5ec69466 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
@@ -25,9 +26,13 @@ import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.BrokenWithSceneContainer
@@ -122,15 +127,22 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
private val fromGlanceableHubTransitionInteractor by lazy {
kosmos.fromGlanceableHubTransitionInteractor
}
+ private val communalSceneTransitionInteractor by lazy {
+ kosmos.communalSceneTransitionInteractor
+ }
private val powerInteractor by lazy { kosmos.powerInteractor }
private val communalInteractor by lazy { kosmos.communalInteractor }
+ private val communalSceneInteractor by lazy { kosmos.communalSceneInteractor }
companion object {
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_SCENE_KTF_REFACTOR,
+ )
+ .andSceneContainer()
}
}
@@ -163,6 +175,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
fromOccludedTransitionInteractor.start()
fromAlternateBouncerTransitionInteractor.start()
fromGlanceableHubTransitionInteractor.start()
+ communalSceneTransitionInteractor.start()
}
@Test
@@ -636,6 +649,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun dozingToGlanceableHub() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
@@ -770,6 +784,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@BrokenWithSceneContainer(339465026)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun goneToGlanceableHub() =
testScope.runTest {
// GIVEN a prior transition has run to GONE
@@ -799,6 +814,29 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
}
@Test
+ @BrokenWithSceneContainer(339465026)
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun goneToGlanceableHub_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GONE
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+ // WHEN the glanceable hub is shown
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.GONE,
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNull() }
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
@DisableSceneContainer
fun alternateBouncerToPrimaryBouncer() =
testScope.runTest {
@@ -941,6 +979,11 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
fun alternateBouncerToGlanceableHub() =
testScope.runTest {
+ // GIVEN the device is idle on the glanceable hub
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ clearInvocations(transitionRepository)
+
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
bouncerRepository.setAlternateVisible(true)
runTransitionAndSetWakefulness(
@@ -951,19 +994,11 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
// GIVEN the primary bouncer isn't showing and device not sleeping
bouncerRepository.setPrimaryShow(false)
- // GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
advanceTimeBy(200L)
- // THEN a transition to LOCKSCREEN should occur
+ // THEN a transition to GLANCEABLE_HUB should occur
assertThat(transitionRepository)
.startedTransition(
ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
@@ -1063,18 +1098,17 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@DisableSceneContainer
fun primaryBouncerToGlanceableHub() =
testScope.runTest {
- // GIVEN a prior transition has run to PRIMARY_BOUNCER
- bouncerRepository.setPrimaryShow(true)
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
-
// GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
runCurrent()
+ // GIVEN a prior transition has run to PRIMARY_BOUNCER
+ bouncerRepository.setPrimaryShow(true)
+ runTransitionAndSetWakefulness(
+ KeyguardState.GLANCEABLE_HUB,
+ KeyguardState.PRIMARY_BOUNCER
+ )
+
// WHEN the primaryBouncer stops showing
bouncerRepository.setPrimaryShow(false)
runCurrent()
@@ -1095,27 +1129,26 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@DisableSceneContainer
fun primaryBouncerToGlanceableHubWhileDreaming() =
testScope.runTest {
+ // GIVEN the device is idle on the glanceable hub
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+
// GIVEN a prior transition has run to PRIMARY_BOUNCER
bouncerRepository.setPrimaryShow(true)
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
+ runTransitionAndSetWakefulness(
+ KeyguardState.GLANCEABLE_HUB,
+ KeyguardState.PRIMARY_BOUNCER
+ )
// GIVEN that we are dreaming and occluded
keyguardRepository.setDreaming(true)
keyguardRepository.setKeyguardOccluded(true)
- // GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
// WHEN the primaryBouncer stops showing
bouncerRepository.setPrimaryShow(false)
runCurrent()
- // THEN a transition to LOCKSCREEN should occur
+ // THEN a transition to GLANCEABLE_HUB should occur
assertThat(transitionRepository)
.startedTransition(
ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
@@ -1219,6 +1252,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@BrokenWithSceneContainer(339465026)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun occludedToGlanceableHub() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1256,6 +1290,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@BrokenWithSceneContainer(339465026)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun occludedToGlanceableHubWhenInitiallyOnHub() =
testScope.runTest {
// GIVEN a device on lockscreen and communal is available
@@ -1293,6 +1328,37 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
}
@Test
+ @BrokenWithSceneContainer(339465026)
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun occludedToGlanceableHub_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen and communal is available
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ // THEN a transition to GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun occludedToAlternateBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to OCCLUDED
@@ -1511,6 +1577,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun dreamingToGlanceableHub() =
testScope.runTest {
// GIVEN a prior transition has run to DREAMING
@@ -1550,24 +1617,41 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
}
@Test
- @BrokenWithSceneContainer(339465026)
- fun lockscreenToOccluded() =
+ @DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun dreamingToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
- // GIVEN a prior transition has run to LOCKSCREEN
- runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreaming(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
runCurrent()
- // WHEN the keyguard is occluded
- keyguardRepository.setKeyguardOccluded(true)
+ // WHEN a transition to the glanceable hub starts
+ val currentScene = CommunalScenes.Blank
+ val targetScene = CommunalScenes.Communal
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+ progress.value = .1f
runCurrent()
- // THEN a transition to OCCLUDED should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromLockscreenTransitionInteractor",
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- animatorAssertion = { it.isNotNull() },
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
)
coroutineContext.cancelChildren()
@@ -1575,10 +1659,10 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@BrokenWithSceneContainer(339465026)
- fun aodToOccluded() =
+ fun lockscreenToOccluded() =
testScope.runTest {
- // GIVEN a prior transition has run to AOD
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
runCurrent()
// WHEN the keyguard is occluded
@@ -1588,8 +1672,8 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
// THEN a transition to OCCLUDED should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromAodTransitionInteractor(isOccluded = true)",
- from = KeyguardState.AOD,
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
animatorAssertion = { it.isNotNull() },
)
@@ -1703,6 +1787,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun lockscreenToGlanceableHub() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1761,6 +1846,48 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun lockscreenToGlanceableHub_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ runCurrent()
+
+ // WHEN a glanceable hub transition starts
+ val currentScene = CommunalScenes.Blank
+ val targetScene = CommunalScenes.Communal
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToLockscreen() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1816,6 +1943,48 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun glanceableHubToLockscreen_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ clearInvocations(transitionRepository)
+
+ // WHEN a transition away from glanceable hub starts
+ val currentScene = CommunalScenes.Communal
+ val targetScene = CommunalScenes.Blank
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDozing() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1838,6 +2007,31 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun glanceableHubToDozing_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ clearInvocations(transitionRepository)
+
+ // WHEN the device begins to sleep
+ powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ @DisableSceneContainer
fun glanceableHubToPrimaryBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1882,6 +2076,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@BrokenWithSceneContainer(339465026)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1912,7 +2107,33 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
}
@Test
+ @BrokenWithSceneContainer(339465026)
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun glanceableHubToOccluded_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ clearInvocations(transitionRepository)
+
+ // WHEN the keyguard is occluded
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
@DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToGone() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1935,6 +2156,32 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun glanceableHubToGone_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ clearInvocations(transitionRepository)
+
+ // WHEN keyguard goes away
+ keyguardRepository.setKeyguardGoingAway(true)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDreaming() =
testScope.runTest {
// GIVEN that we are dreaming and not dozing
@@ -1963,7 +2210,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
isUserInputOngoing = flowOf(false),
)
)
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
assertThat(transitionRepository)
@@ -1977,6 +2224,52 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
coroutineContext.cancelChildren()
}
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun glanceableHubToDreaming_communalKtfRefactor() =
+ testScope.runTest {
+ // GIVEN that we are dreaming and not dozing
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ runCurrent()
+ clearInvocations(transitionRepository)
+
+ // WHEN a transition away from glanceable hub starts
+ val currentScene = CommunalScenes.Communal
+ val targetScene = CommunalScenes.Blank
+
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = flowOf(0f, 0.1f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
private suspend fun TestScope.runTransitionAndSetWakefulness(
from: KeyguardState,
to: KeyguardState
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 278c90a5f068..fd2e33504ec9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -159,8 +159,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
)
assertThat(values[0]).isEqualTo(1f)
- // Should fade to zero between here
assertThat(values[1]).isEqualTo(0f)
+ // Should always finish with 1f to show HUNs
+ assertThat(values[2]).isEqualTo(1f)
}
@Test
@@ -177,7 +178,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
testScope,
)
- assertThat(values.size).isEqualTo(2)
+ assertThat(values.size).isEqualTo(3)
// Shade stays open, and alpha should remain visible
values.forEach { assertThat(it).isEqualTo(1f) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index a120bdc0b743..540a85aeb695 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -175,12 +175,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
assertThat(isVisible).isFalse()
- kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
+ kosmos.headsUpNotificationRepository.setNotifications(
buildNotificationRows(isPinned = true)
+ )
assertThat(isVisible).isTrue()
- kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
+ kosmos.headsUpNotificationRepository.setNotifications(
buildNotificationRows(isPinned = false)
+ )
assertThat(isVisible).isFalse()
}
@@ -1699,8 +1701,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
return transitionStateFlow
}
- private fun buildNotificationRows(isPinned: Boolean = false): Set<HeadsUpRowRepository> =
- setOf(
+ private fun buildNotificationRows(isPinned: Boolean = false): List<HeadsUpRowRepository> =
+ listOf(
fakeHeadsUpRowRepository(key = "0", isPinned = isPinned),
fakeHeadsUpRowRepository(key = "1", isPinned = isPinned),
fakeHeadsUpRowRepository(key = "2", isPinned = isPinned),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
new file mode 100644
index 000000000000..8810ade1d851
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2024 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:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.Notification
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_LOW
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.modifyEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ testDispatcher = UnconfinedTestDispatcher()
+ statusBarStateController =
+ mock<SysuiStatusBarStateController>().also { mock ->
+ doAnswer { statusBarState.ordinal }.whenever(mock).state
+ }
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
+ private val notifPipeline: NotifPipeline = mock()
+ private var statusBarState: StatusBarState = StatusBarState.KEYGUARD
+
+ @Test
+ fun topUnseenSectioner() {
+ val solo = NotificationEntryBuilder().setTag("solo").build()
+ val child1 = NotificationEntryBuilder().setTag("child1").build()
+ val child2 = NotificationEntryBuilder().setTag("child2").build()
+ val parent = NotificationEntryBuilder().setTag("parent").build()
+ val group = GroupEntryBuilder().addChild(child1).addChild(child2).setSummary(parent).build()
+
+ runCoordinatorTest {
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = solo.key
+ assertThat(topUnseenSectioner.isInSection(solo)).isTrue()
+ assertThat(topUnseenSectioner.isInSection(child1)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(parent)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(group)).isFalse()
+
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child1.key
+ assertThat(topUnseenSectioner.isInSection(solo)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(child1)).isTrue()
+ assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(parent)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(group)).isTrue()
+
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = parent.key
+ assertThat(topUnseenSectioner.isInSection(solo)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(child1)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(parent)).isTrue()
+ assertThat(topUnseenSectioner.isInSection(group)).isTrue()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = solo.key
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = null
+ assertThat(topUnseenSectioner.isInSection(solo)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(child1)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(child2)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(parent)).isFalse()
+ assertThat(topUnseenSectioner.isInSection(group)).isFalse()
+ }
+ }
+
+ @Test
+ fun topOngoingSectioner() {
+ val solo = NotificationEntryBuilder().setTag("solo").build()
+ val child1 = NotificationEntryBuilder().setTag("child1").build()
+ val child2 = NotificationEntryBuilder().setTag("child2").build()
+ val parent = NotificationEntryBuilder().setTag("parent").build()
+ val group = GroupEntryBuilder().addChild(child1).addChild(child2).setSummary(parent).build()
+
+ runCoordinatorTest {
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = solo.key
+ assertThat(topOngoingSectioner.isInSection(solo)).isTrue()
+ assertThat(topOngoingSectioner.isInSection(child1)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(parent)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(group)).isFalse()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = child1.key
+ assertThat(topOngoingSectioner.isInSection(solo)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(child1)).isTrue()
+ assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(parent)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(group)).isTrue()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = parent.key
+ assertThat(topOngoingSectioner.isInSection(solo)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(child1)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(parent)).isTrue()
+ assertThat(topOngoingSectioner.isInSection(group)).isTrue()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = null
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = solo.key
+ assertThat(topOngoingSectioner.isInSection(solo)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(child1)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(child2)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(parent)).isFalse()
+ assertThat(topOngoingSectioner.isInSection(group)).isFalse()
+ }
+ }
+
+ @Test
+ fun testPromoter() {
+ val child1 = NotificationEntryBuilder().setTag("child1").build()
+ val child2 = NotificationEntryBuilder().setTag("child2").build()
+ val child3 = NotificationEntryBuilder().setTag("child3").build()
+ val parent = NotificationEntryBuilder().setTag("parent").build()
+ GroupEntryBuilder()
+ .addChild(child1)
+ .addChild(child2)
+ .addChild(child3)
+ .setSummary(parent)
+ .build()
+
+ runCoordinatorTest {
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = null
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = null
+ assertThat(promoter.shouldPromoteToTopLevel(child1)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(child2)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = child1.key
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = null
+ assertThat(promoter.shouldPromoteToTopLevel(child1)).isTrue()
+ assertThat(promoter.shouldPromoteToTopLevel(child2)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = null
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child2.key
+ assertThat(promoter.shouldPromoteToTopLevel(child1)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(child2))
+ .isEqualTo(NotificationMinimalismPrototype.ungroupTopUnseen)
+ assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value = child1.key
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child2.key
+ assertThat(promoter.shouldPromoteToTopLevel(child1)).isTrue()
+ assertThat(promoter.shouldPromoteToTopLevel(child2))
+ .isEqualTo(NotificationMinimalismPrototype.ungroupTopUnseen)
+ assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
+ assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
+ }
+ }
+
+ @Test
+ fun topOngoingIdentifier() {
+ val solo1 = defaultEntryBuilder().setTag("solo1").setRank(1).build()
+ val solo2 = defaultEntryBuilder().setTag("solo2").setRank(2).build()
+ val parent = defaultEntryBuilder().setTag("parent").setRank(3).build()
+ val child1 = defaultEntryBuilder().setTag("child1").setRank(4).build()
+ val child2 = defaultEntryBuilder().setTag("child2").setRank(5).build()
+ val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build()
+ val listEntryList = listOf(group, solo1, solo2)
+
+ runCoordinatorTest {
+ // TEST: base case - no entries in the list
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(emptyList())
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: none of these are unseen or ongoing yet, so don't pick them
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: when solo2 is the only one colorized, it gets picked up
+ solo2.setColorizedFgs(true)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(solo2.key)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: once solo1 is colorized, it takes priority for being ranked higher
+ solo1.setColorizedFgs(true)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(solo1.key)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: changing just the rank of solo1 causes it to pick up solo2 instead
+ solo1.modifyEntry { setRank(20) }
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(solo2.key)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: switching to SHADE disables the whole thing
+ statusBarState = StatusBarState.SHADE
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: switching back to KEYGUARD picks up the same entry again
+ statusBarState = StatusBarState.KEYGUARD
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(solo2.key)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: updating to not colorized revokes the top-ongoing status
+ solo2.setColorizedFgs(false)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(solo1.key)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: updating the importance to LOW revokes top-ongoing status
+ solo1.modifyEntry { setImportance(IMPORTANCE_LOW) }
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+ }
+ }
+
+ @Test
+ fun topUnseenIdentifier() {
+ val solo1 = defaultEntryBuilder().setTag("solo1").setRank(1).build()
+ val solo2 = defaultEntryBuilder().setTag("solo2").setRank(2).build()
+ val parent = defaultEntryBuilder().setTag("parent").setRank(4).build()
+ val child1 = defaultEntryBuilder().setTag("child1").setRank(5).build()
+ val child2 = defaultEntryBuilder().setTag("child2").setRank(6).build()
+ val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build()
+ val listEntryList = listOf(group, solo1, solo2)
+ val notificationEntryList = listOf(solo1, solo2, parent, child1, child2)
+
+ runCoordinatorTest {
+ // All entries are added (and now unseen)
+ notificationEntryList.forEach { collectionListener.onEntryAdded(it) }
+
+ // TEST: Filtered out entries are ignored
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(emptyList())
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: top-ranked unseen child is selected (not the summary)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(group))
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(child1.key)
+
+ // TEST: top-ranked entry is picked
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+ // TEST: if top-ranked unseen is colorized, fall back to #2 ranked unseen
+ solo1.setColorizedFgs(true)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(solo1.key)
+ assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+ // TEST: no more colorized entries
+ solo1.setColorizedFgs(false)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+ // TEST: if the rank of solo1 is reduced, solo2 will be preferred
+ solo1.modifyEntry { setRank(3) }
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+ // TEST: switching to SHADE state will disable the entire selector
+ statusBarState = StatusBarState.SHADE
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: switching back to KEYGUARD re-enables the selector
+ statusBarState = StatusBarState.KEYGUARD
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+ // TEST: QS Expansion does not mark entries as seen
+ setShadeAndQsExpansionThenWait(0f, 1f)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+ // TEST: Shade expansion does mark entries as seen
+ setShadeAndQsExpansionThenWait(1f, 0f)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: Entries updated while shade is expanded are NOT marked unseen
+ collectionListener.onEntryUpdated(solo1)
+ collectionListener.onEntryUpdated(solo2)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
+ // TEST: Entries updated after shade is collapsed ARE marked unseen
+ setShadeAndQsExpansionThenWait(0f, 0f)
+ collectionListener.onEntryUpdated(solo1)
+ collectionListener.onEntryUpdated(solo2)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+ // TEST: low importance disqualifies the entry for top unseen
+ solo2.modifyEntry { setImportance(IMPORTANCE_LOW) }
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(solo1.key)
+ }
+ }
+
+ @Test
+ fun topUnseenIdentifier_headsUpMarksSeen() {
+ val solo1 = defaultEntryBuilder().setTag("solo1").setRank(1).build()
+ val solo2 = defaultEntryBuilder().setTag("solo2").setRank(2).build()
+ val listEntryList = listOf(solo1, solo2)
+ val notificationEntryList = listOf(solo1, solo2)
+
+ val hunRepo1 = solo1.fakeHeadsUpRowRepository()
+ val hunRepo2 = solo2.fakeHeadsUpRowRepository()
+
+ runCoordinatorTest {
+ // All entries are added (and now unseen)
+ notificationEntryList.forEach { collectionListener.onEntryAdded(it) }
+
+ // TEST: top-ranked entry is picked
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+ // TEST: heads up state and waiting isn't enough to be seen
+ kosmos.headsUpNotificationRepository.orderedHeadsUpRows.value =
+ listOf(hunRepo1, hunRepo2)
+ testScheduler.advanceTimeBy(1.seconds)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+ // TEST: even being pinned doesn't take effect immediately
+ hunRepo1.isPinned.value = true
+ testScheduler.advanceTimeBy(0.5.seconds)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopUnseenKey().isEqualTo(solo1.key)
+
+ // TEST: after being pinned a full second, solo1 is seen
+ testScheduler.advanceTimeBy(0.5.seconds)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopUnseenKey().isEqualTo(solo2.key)
+
+ // TEST: repeat; being heads up and pinned for 1 second triggers seen
+ kosmos.headsUpNotificationRepository.orderedHeadsUpRows.value = listOf(hunRepo2)
+ hunRepo1.isPinned.value = false
+ hunRepo2.isPinned.value = true
+ testScheduler.advanceTimeBy(1.seconds)
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
+ assertThatTopUnseenKey().isEqualTo(null)
+ }
+ }
+
+ private fun NotificationEntry.fakeHeadsUpRowRepository() =
+ FakeHeadsUpRowRepository(key = key, elementKey = Any())
+
+ private fun KeyguardCoordinatorTestScope.setShadeAndQsExpansionThenWait(
+ shadeExpansion: Float,
+ qsExpansion: Float
+ ) {
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion, qsExpansion)
+ // The coordinator waits a fraction of a second for the shade expansion to stick.
+ testScheduler.advanceTimeBy(1.seconds)
+ }
+
+ private fun defaultEntryBuilder() = NotificationEntryBuilder().setImportance(IMPORTANCE_DEFAULT)
+
+ private fun runCoordinatorTest(testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit) {
+ kosmos.lockScreenMinimalismCoordinator.attach(notifPipeline)
+ kosmos.testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
+ KeyguardCoordinatorTestScope(
+ kosmos.lockScreenMinimalismCoordinator,
+ kosmos.testScope,
+ kosmos.fakeSettings,
+ )
+ .testBlock()
+ }
+ }
+
+ private inner class KeyguardCoordinatorTestScope(
+ private val coordinator: LockScreenMinimalismCoordinator,
+ private val scope: TestScope,
+ private val fakeSettings: FakeSettings,
+ ) : CoroutineScope by scope {
+ fun assertThatTopOngoingKey(): StringSubject {
+ return assertThat(
+ kosmos.activeNotificationListRepository.topOngoingNotificationKey.value
+ )
+ }
+
+ fun assertThatTopUnseenKey(): StringSubject {
+ return assertThat(
+ kosmos.activeNotificationListRepository.topUnseenNotificationKey.value
+ )
+ }
+
+ val testScheduler: TestCoroutineScheduler
+ get() = scope.testScheduler
+
+ val promoter: NotifPromoter
+ get() = coordinator.unseenNotifPromoter
+
+ val topUnseenSectioner: NotifSectioner
+ get() = coordinator.topUnseenSectioner
+
+ val topOngoingSectioner: NotifSectioner
+ get() = coordinator.topOngoingSectioner
+
+ val onBeforeTransformGroupsListener: OnBeforeTransformGroupsListener =
+ argumentCaptor { verify(notifPipeline).addOnBeforeTransformGroupsListener(capture()) }
+ .lastValue
+
+ val collectionListener: NotifCollectionListener =
+ argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
+
+ var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
+ get() =
+ fakeSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ ) == 1
+ set(value) {
+ fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ if (value) 1 else 2,
+ UserHandle.USER_CURRENT,
+ )
+ }
+ }
+
+ companion object {
+
+ private fun NotificationEntry.setColorizedFgs(colorized: Boolean) {
+ sbn.notification.setColorizedFgs(colorized)
+ }
+
+ private fun Notification.setColorizedFgs(colorized: Boolean) {
+ extras.putBoolean(Notification.EXTRA_COLORIZED, colorized)
+ flags =
+ if (colorized) {
+ flags or Notification.FLAG_FOREGROUND_SERVICE
+ } else {
+ flags and Notification.FLAG_FOREGROUND_SERVICE.inv()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index ce134e64bf57..75ecb2c5307c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -41,8 +41,12 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -91,6 +95,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
+ @Mock private VisualStabilityCoordinatorLogger mLogger;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
@@ -128,7 +133,9 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mVisibilityLocationProvider,
mVisualStabilityProvider,
mWakefulnessLifecycle,
- mKosmos.getCommunalInteractor());
+ mKosmos.getCommunalInteractor(),
+ mKosmos.getKeyguardTransitionInteractor(),
+ mLogger);
mCoordinator.attach(mNotifPipeline);
mTestScope.getTestScheduler().runCurrent();
@@ -241,6 +248,38 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() {
+ // GIVEN the panel true expanded and device isn't pulsing
+ setFullyDozed(false);
+ setSleepy(false);
+ setLockscreenShowing(0.5f);
+ setPulsing(false);
+
+ // THEN group changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+ assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
+
+ // THEN section changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+ }
+
+ @Test
+ public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() {
+ // GIVEN the panel true expanded and device isn't pulsing
+ setFullyDozed(false);
+ setSleepy(false);
+ setLockscreenShowing(1.0f);
+ setPulsing(false);
+
+ // THEN group changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+ assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
+
+ // THEN section changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+ }
+
+ @Test
public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() {
// GIVEN the device is pulsing and screen is off
setFullyDozed(true);
@@ -614,7 +653,37 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
private void setPanelExpanded(boolean expanded) {
- mStatusBarStateListener.onExpandedChanged(expanded);
+ setPanelExpandedAndLockscreenShowing(expanded, /* lockscreenShowing = */ 0.0f);
}
+ private void setLockscreenShowing(float lockscreenShowing) {
+ setPanelExpandedAndLockscreenShowing(/* panelExpanded = */ false, lockscreenShowing);
+ }
+
+ private void setPanelExpandedAndLockscreenShowing(boolean panelExpanded,
+ float lockscreenShowing) {
+ if (SceneContainerFlag.isEnabled()) {
+ mStatusBarStateListener.onExpandedChanged(panelExpanded);
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope,
+ makeLockscreenTransitionStep(lockscreenShowing),
+ /* validateStep = */ false);
+ } else {
+ mStatusBarStateListener.onExpandedChanged(panelExpanded || lockscreenShowing > 0.0f);
+ }
+ }
+
+ private TransitionStep makeLockscreenTransitionStep(float value) {
+ if (value <= 0.0f) {
+ return new TransitionStep(KeyguardState.GONE);
+ } else if (value >= 1.0f) {
+ return new TransitionStep(KeyguardState.LOCKSCREEN);
+ } else {
+ return new TransitionStep(
+ KeyguardState.GONE,
+ KeyguardState.LOCKSCREEN,
+ value,
+ TransitionState.RUNNING);
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 8b4265f552fe..14134ccc34d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRo
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
index 5e87f4663d76..61873ad294e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.row.ui.viewmodel
+import android.app.Notification
import android.app.PendingIntent
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -90,7 +91,8 @@ class TimerViewModelTest : SysuiTestCase() {
name: String = "example",
timeRemaining: Duration = Duration.ofMinutes(3),
resumeIntent: PendingIntent? = null,
- resetIntent: PendingIntent? = null
+ addMinuteAction: Notification.Action? = null,
+ resetAction: Notification.Action? = null
) =
TimerContentModel(
icon = icon,
@@ -99,7 +101,8 @@ class TimerViewModelTest : SysuiTestCase() {
Paused(
timeRemaining = timeRemaining,
resumeIntent = resumeIntent,
- resetIntent = resetIntent,
+ addMinuteAction = addMinuteAction,
+ resetAction = resetAction,
)
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f8e633731c42..f96cf1011fb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -43,7 +43,6 @@ import com.android.systemui.statusbar.notification.data.repository.setActiveNoti
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.statusbar.policy.fakeConfigurationController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 71cd95f828ef..6f099310f9f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -1096,7 +1096,8 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
)
)
runCurrent()
- assertThat(alpha).isEqualTo(0f)
+ // Resets to 1f after communal scene is hidden
+ assertThat(alpha).isEqualTo(1f)
}
@Test
@@ -1151,7 +1152,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
)
)
runCurrent()
- assertThat(alpha).isEqualTo(0f)
+ assertThat(alpha).isEqualTo(1f)
}
@Test
@@ -1208,7 +1209,8 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
)
)
runCurrent()
- assertThat(alpha).isEqualTo(0f)
+ // Resets to 1f after communal scene is hidden
+ assertThat(alpha).isEqualTo(1f)
}
@Test
@@ -1263,7 +1265,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
)
)
runCurrent()
- assertThat(alpha).isEqualTo(0f)
+ assertThat(alpha).isEqualTo(1f)
}
private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index b643968c6322..c3c5a48dc197 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -50,6 +50,7 @@ class ActivityStarterImplTest : SysuiTestCase() {
statusBarStateController = statusBarStateController,
mainExecutor = mainExecutor,
legacyActivityStarter = { legacyActivityStarterInternal },
+ activityStarterInternal = { activityStarterInternal },
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 10a2f6407849..179799503ac0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -147,6 +147,48 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
}
@Test
+ fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runAfterKeyguardGone() {
+ val intent = mock(Intent::class.java)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSettingsInteractor.isCommunalFlagEnabled()).thenReturn(false)
+
+ underTest.startActivityDismissingKeyguard(intent, dismissShade = true)
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), any(), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any())
+ }
+
+ @Test
+ fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runImmediately() {
+ val intent = mock(Intent::class.java)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSettingsInteractor.isCommunalFlagEnabled()).thenReturn(true)
+
+ underTest.startActivityDismissingKeyguard(intent, dismissShade = true)
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), any(), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ verify(statusBarKeyguardViewManager, never()).addAfterKeyguardGoneRunnable(any())
+ verify(activityTransitionAnimator)
+ .startIntentWithAnimation(eq(null), eq(false), eq(null), eq(false), any())
+ }
+
+ @Test
fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
val pendingIntent = mock(PendingIntent::class.java)
`when`(pendingIntent.isActivity).thenReturn(true)
@@ -231,7 +273,6 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
// extra activity options to set on pending intent
val activityOptions = mock(ActivityOptions::class.java)
activityOptions.splashScreenStyle = SPLASH_SCREEN_STYLE_SOLID_COLOR
- activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission = false
val bundleCaptor = argumentCaptor<Bundle>()
startPendingIntentMaybeDismissingKeyguard(
@@ -255,7 +296,8 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
bundleCaptor.capture()
)
val options = ActivityOptions.fromBundle(bundleCaptor.firstValue)
- assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission).isFalse()
+ assertThat(options.getPendingIntentBackgroundActivityStartMode())
+ .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
assertThat(options.splashScreenStyle).isEqualTo(SPLASH_SCREEN_STYLE_SOLID_COLOR)
}
@@ -342,7 +384,6 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
)
}
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
@Test
fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() {
val parent = FrameLayout(context)
@@ -389,7 +430,6 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
)
}
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
@Test
fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() {
val parent = FrameLayout(context)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 495ab61600f9..8f9da3b2e1e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -180,6 +180,23 @@ class AvalancheControllerTest : SysuiTestCase() {
}
@Test
+ fun testDelete_untracked_runnableRuns() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // None showing
+ mAvalancheController.headsUpEntryShowing = null
+
+ // Nothing is next
+ mAvalancheController.clearNext()
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
fun testDelete_isNext_removedFromNext_runnableNotRun() {
// Entry is next
val headsUpEntry = createHeadsUpEntry(id = 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index d0ddbffecf9a..b91bde4fb417 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.policy
import android.content.Context
import android.os.Handler
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
@@ -33,6 +34,7 @@ import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -42,6 +44,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
import junit.framework.Assert
@@ -237,6 +240,36 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
}
@Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testShowNotification_reorderNotAllowed_notPulsing_seenInShadeTrue() {
+ whenever(mVSProvider.isReorderingAllowed).thenReturn(false)
+ val hmp = createHeadsUpManagerPhone()
+
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val row = mock<ExpandableNotificationRow>()
+ whenever(row.showingPulsing()).thenReturn(false)
+ notifEntry.row = row
+
+ hmp.showNotification(notifEntry)
+ Assert.assertTrue(notifEntry.isSeenInShade)
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testShowNotification_reorderAllowed_notPulsing_seenInShadeFalse() {
+ whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
+ val hmp = createHeadsUpManagerPhone()
+
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val row = mock<ExpandableNotificationRow>()
+ whenever(row.showingPulsing()).thenReturn(false)
+ notifEntry.row = row
+
+ hmp.showNotification(notifEntry)
+ Assert.assertFalse(notifEntry.isSeenInShade)
+ }
+
+ @Test
fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
testScope.runTest {
// GIVEN
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml b/packages/SystemUI/res/drawable/checkbox_circle_shape.xml
index 9128dca2080b..2b987e2d1fb1 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml
+++ b/packages/SystemUI/res/drawable/checkbox_circle_shape.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 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,15 +14,19 @@
~ 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="com.android.frameworks.core.batteryusagestatsprototests">
-
- <uses-permission android:name="android.permission.BATTERY_STATS"/>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.frameworks.core.batteryusagestatsprototests"
- android:label="BatteryUsageStats Proto Tests" />
-
-</manifest>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:bottom="12dp"
+ android:left="12dp"
+ android:right="12dp"
+ android:top="12dp">
+ <selector>
+ <item
+ android:drawable="@drawable/ic_check_circle_filled_24dp"
+ android:state_checked="true" />
+ <item
+ android:drawable="@drawable/ic_circle_outline_24dp"
+ android:state_checked="false" />
+ </selector>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml b/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml
new file mode 100644
index 000000000000..16e2a3db2e95
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?androidprv:attr/materialColorPrimary"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10c5.52,0 10,-4.48 10,-10S17.52,2 12,2zM10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l5.66,-5.66l1.41,1.41L10.59,16.6z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml b/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml
new file mode 100644
index 000000000000..82fa4f08d0c8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?androidprv:attr/materialColorPrimary"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index 5191895549b6..d7b94ec015ac 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -58,9 +58,10 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="16dp"
+ android:button="@drawable/checkbox_circle_shape"
android:checked="true"
android:text="@string/backlinks_include_link"
- android:textColor="?android:textColorSecondary"
+ android:textColor="?androidprv:attr/materialColorOnBackground"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/preview"
app:layout_constraintStart_toEndOf="@id/cancel"
diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
index f2bfbe5c960d..3a679e3c16cb 100644
--- a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
+++ b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
@@ -33,7 +33,6 @@
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
- android:src="@drawable/ic_close"
app:tint="@android:color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/label"
@@ -88,11 +87,10 @@
/>
<com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
+ style="@*android:style/NotificationEmphasizedAction"
android:id="@+id/mainButton"
android:layout_width="124dp"
android:layout_height="wrap_content"
- tools:text="Reset"
- tools:drawableStart="@android:drawable/ic_menu_add"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/altButton"
app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
@@ -101,15 +99,23 @@
/>
<com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
+ style="@*android:style/NotificationEmphasizedAction"
android:id="@+id/altButton"
- tools:text="Reset"
- tools:drawableStart="@android:drawable/ic_menu_add"
- android:drawablePadding="2dp"
- android:drawableTint="@android:color/white"
android:layout_width="124dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
app:layout_constraintStart_toEndOf="@id/mainButton"
+ app:layout_constraintEnd_toEndOf="@id/resetButton"
+ android:paddingEnd="4dp"
+ />
+
+ <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
+ style="@*android:style/NotificationEmphasizedAction"
+ android:id="@+id/resetButton"
+ android:layout_width="124dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
+ app:layout_constraintStart_toEndOf="@id/altButton"
app:layout_constraintEnd_toEndOf="parent"
android:paddingEnd="4dp"
/>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 763930db1d55..07a40c86d03a 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,9 +33,4 @@
<!-- Whether the user switcher chip shows in the status bar. When true, the multi user
avatar will no longer show on the lockscreen -->
<bool name="flag_user_switcher_chip">false</bool>
-
- <!-- Whether the battery icon is allowed to display a shield when battery life is being
- protected. -->
- <bool name="flag_battery_shield_icon">false</bool>
-
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8322b6c85aed..acc12d7fdaf0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1431,10 +1431,10 @@
<string name="no_unseen_notif_text">No new notifications</string>
<!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] -->
- <string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string>
+ <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string>
<!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
- <string name="adaptive_notification_edu_hun_text">Your device now lowers the volume and reduces pop-ups on the screen for up to two minutes when you receive many notifications in a short time span.</string>
+ <string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string>
<!-- Action label for going to adaptive notification settings [CHAR LIMIT=20] -->
<string name="go_to_adaptive_notification_settings">Turn off</string>
@@ -3677,4 +3677,31 @@
<string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
<!-- Label for volume undo action [CHAR LIMIT=NONE] -->
<string name="volume_undo_action">Undo</string>
+
+ <!-- Keyboard touchpad contextual education strings-->
+ <!-- Education toast text for Back [CHAR_LIMIT=100] -->
+ <string name="back_edu_toast_content">To go back, swipe left or right with three fingers on the touchpad</string>
+ <!-- Education toast text for Home [CHAR_LIMIT=100] -->
+ <string name="home_edu_toast_content">To go home, swipe up with three fingers on the touchpad</string>
+ <!-- Education toast text for Overview [CHAR_LIMIT=100] -->
+ <string name="overview_edu_toast_content">To view recent apps, swipe up and hold with three fingers on the touchpad</string>
+ <!-- Education toast text for All Apps [CHAR_LIMIT=100] -->
+ <string name="all_apps_edu_toast_content">To view all your apps, press the action key on your keyboard</string>
+
+ <!-- Education notification title for Back [CHAR_LIMIT=100] -->
+ <string name="back_edu_notification_title">Use your touchpad to go back</string>
+ <!-- Education notification text for Back [CHAR_LIMIT=100] -->
+ <string name="back_edu_notification_content">Swipe left or right using three fingers. Tap to learn more gestures.</string>
+ <!-- Education notification title for Home [CHAR_LIMIT=100] -->
+ <string name="home_edu_notification_title">Use your touchpad to go home</string>
+ <!-- Education notification text for Home [CHAR_LIMIT=100] -->
+ <string name="home_edu_notification_content">Swipe up using three fingers. Tap to learn more gestures.</string>
+ <!-- Education notification title for Overview [CHAR_LIMIT=100] -->
+ <string name="overview_edu_notification_title">Use your touchpad to view recent apps</string>
+ <!-- Education notification text for Overview [CHAR_LIMIT=100] -->
+ <string name="overview_edu_notification_content">Swipe up and hold using three fingers. Tap to learn more gestures.</string>
+ <!-- Education notification title for All Apps [CHAR_LIMIT=100] -->
+ <string name="all_apps_edu_notification_title">Use your keyboard to view all apps</string>
+ <!-- Education notification text for All Apps [CHAR_LIMIT=100] -->
+ <string name="all_apps_edu_notification_content">Press the action key at any time. Tap to learn more gestures.</string>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 845ca5e8b9ec..3019fe796d7d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -344,7 +344,7 @@ public class ActivityManagerWrapper {
* Shows a voice session identified by {@code token}
* @return true if the session was shown, false otherwise
*/
- public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags,
+ public boolean showVoiceSession(IBinder token, @NonNull Bundle args, int flags,
@Nullable String attributionTag) {
IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 484e758bf973..4ef1f93481f7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,6 +124,8 @@ public class QuickStepContract {
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
+ // PiP animation is running
+ public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
@@ -175,6 +177,7 @@ public class QuickStepContract {
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
+ SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
})
public @interface SystemUiStateFlags {}
@@ -280,6 +283,9 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
+ if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
+ str.add("disable_gesture_pip_animating");
+ }
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 6e5e44e45548..e9c9bc7799a5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -363,6 +363,16 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
}
@MainThread
+ void updateSettingsButtonStatus(int displayId,
+ @WindowMagnificationSettings.MagnificationSize int index) {
+ final MagnificationSettingsController magnificationSettingsController =
+ mMagnificationSettingsSupplier.get(displayId);
+ if (magnificationSettingsController != null) {
+ magnificationSettingsController.updateSettingsButtonStatusOnRestore(index);
+ }
+ }
+
+ @MainThread
void toggleSettingsPanelVisibility(int displayId) {
final MagnificationSettingsController magnificationSettingsController =
mMagnificationSettingsSupplier.get(displayId);
@@ -446,6 +456,11 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
@VisibleForTesting
final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
@Override
+ public void onWindowMagnifierBoundsRestored(int displayId, int index) {
+ mHandler.post(() -> updateSettingsButtonStatus(displayId, index));
+ }
+
+ @Override
public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
if (mMagnificationConnectionImpl != null) {
mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index ed7062b5a335..caf55174b6b5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -100,6 +100,10 @@ public class MagnificationSettingsController implements ComponentCallbacks {
mWindowMagnificationSettings.toggleSettingsPanelVisibility();
}
+ void updateSettingsButtonStatusOnRestore(@MagnificationSize int index) {
+ mWindowMagnificationSettings.updateSelectedButton(index);
+ }
+
void closeMagnificationSettings() {
mContext.unregisterComponentCallbacks(this);
mWindowMagnificationSettings.hideSettingPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index b37ba8994e44..3828f9f66857 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -127,6 +127,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private final WindowManager mWm;
private float mScale;
+ private int mSettingsButtonIndex = MagnificationSize.DEFAULT;
/**
* MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained
@@ -436,6 +437,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (!mMagnificationSizeScaleOptions.contains(index)) {
return;
}
+ mSettingsButtonIndex = index;
int size = getMagnificationWindowSizeFromIndex(index);
setWindowSize(size, size);
}
@@ -446,6 +448,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return (int) (initSize * scale) - (int) (initSize * scale) % 2;
}
+ int getMagnificationFrameSizeFromIndex(@MagnificationSize int index) {
+ return getMagnificationWindowSizeFromIndex(index) - 2 * mMirrorSurfaceMargin;
+ }
+
void setEditMagnifierSizeMode(boolean enable) {
mEditSizeEnable = enable;
applyResourcesValues();
@@ -457,8 +463,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (!enable) {
// Keep the magnifier size when exiting edit mode
- mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(
+ mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(
+ mSettingsButtonIndex,
new Size(mMagnificationFrame.width(), mMagnificationFrame.height()));
+ } else {
+ mSettingsButtonIndex = MagnificationSize.CUSTOM;
}
}
@@ -944,7 +953,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
- mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
+ mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(
+ mSettingsButtonIndex, new Size(width, height));
// Sets the initial frame area for the mirror and place it to the given center on the
// display.
@@ -954,6 +964,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private Size restoreMagnificationWindowFrameSizeIfPossible() {
+ if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+ return restoreMagnificationWindowFrameIndexAndSizeIfPossible();
+ }
+
if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
return getDefaultMagnificationWindowFrameSize();
}
@@ -961,8 +975,37 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
}
+ private Size restoreMagnificationWindowFrameIndexAndSizeIfPossible() {
+ if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
+ notifyWindowSizeRestored(MagnificationSize.DEFAULT);
+ return getDefaultMagnificationWindowFrameSize();
+ }
+
+ // This will return DEFAULT index if the stored preference is in an invalid format.
+ // Therefore, except CUSTOM, we would like to calculate the window width and height based
+ // on the restored MagnificationSize index.
+ int restoredIndex = mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
+ notifyWindowSizeRestored(restoredIndex);
+ if (restoredIndex == MagnificationSize.CUSTOM) {
+ return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
+ }
+
+ int restoredSize = getMagnificationFrameSizeFromIndex(restoredIndex);
+ return new Size(restoredSize, restoredSize);
+ }
+
+ private void notifyWindowSizeRestored(@MagnificationSize int index) {
+ mSettingsButtonIndex = index;
+ if (isActivated()) {
+ // Send the callback only if the window magnification is activated. The check is to
+ // avoid updating the settings panel in the cases that window magnification is not yet
+ // activated such as during the constructor initialization of this class.
+ mWindowMagnifierCallback.onWindowMagnifierBoundsRestored(mDisplayId, index);
+ }
+ }
+
private Size getDefaultMagnificationWindowFrameSize() {
- final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM)
+ final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.DEFAULT)
- 2 * mMirrorSurfaceMargin;
return new Size(defaultSize, defaultSize);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
index e83e85e1af1a..ee36c6e8ad35 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
@@ -16,10 +16,14 @@
package com.android.systemui.accessibility;
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Size;
+import com.android.systemui.Flags;
+
/**
* Class to handle SharedPreference for window magnification size.
*/
@@ -47,9 +51,15 @@ final class WindowMagnificationFrameSizePrefs {
/**
* Saves the window frame size for current screen density.
*/
- public void saveSizeForCurrentDensity(Size size) {
- mWindowMagnificationSizePreferences.edit()
- .putString(getKey(), size.toString()).apply();
+ public void saveIndexAndSizeForCurrentDensity(int index, Size size) {
+ if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+ mWindowMagnificationSizePreferences.edit()
+ .putString(getKey(),
+ WindowMagnificationFrameSpec.serialize(index, size)).apply();
+ } else {
+ mWindowMagnificationSizePreferences.edit()
+ .putString(getKey(), size.toString()).apply();
+ }
}
/**
@@ -62,10 +72,32 @@ final class WindowMagnificationFrameSizePrefs {
}
/**
+ * Gets the index preference for current screen density. Returns DEFAULT if no preference
+ * is found.
+ */
+ public @MagnificationSize int getIndexForCurrentDensity() {
+ final String spec = mWindowMagnificationSizePreferences.getString(getKey(), null);
+ if (spec == null) {
+ return MagnificationSize.DEFAULT;
+ }
+ try {
+ return WindowMagnificationFrameSpec.deserialize(spec).getIndex();
+ } catch (NumberFormatException e) {
+ return MagnificationSize.DEFAULT;
+ }
+ }
+
+ /**
* Gets the size preference for current screen density.
*/
public Size getSizeForCurrentDensity() {
- return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+ if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+ return WindowMagnificationFrameSpec
+ .deserialize(mWindowMagnificationSizePreferences.getString(getKey(), null))
+ .getSize();
+ } else {
+ return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt
new file mode 100644
index 000000000000..c261a997aec5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.systemui.accessibility
+
+import android.util.Size
+
+data class WindowMagnificationFrameSpec(val index: Int, val size: Size) {
+
+ companion object {
+ private fun throwInvalidWindowMagnificationFrameSpec(s: String?): Nothing {
+ throw NumberFormatException("Invalid WindowMagnificationFrameSpec: \"$s\"")
+ }
+
+ @JvmStatic fun serialize(index: Int, size: Size) = "$index,$size"
+
+ @JvmStatic
+ fun deserialize(s: String): WindowMagnificationFrameSpec {
+ val separatorIndex = s.indexOf(',')
+ if (separatorIndex < 0) {
+ throwInvalidWindowMagnificationFrameSpec(s)
+ }
+ return try {
+ WindowMagnificationFrameSpec(
+ s.substring(0, separatorIndex).toInt(),
+ Size.parseSize(s.substring(separatorIndex + 1))
+ )
+ } catch (e: NumberFormatException) {
+ throwInvalidWindowMagnificationFrameSpec(s)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 5f6f21a6845b..99d966dfd9aa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -58,6 +58,7 @@ import android.widget.Switch;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -98,7 +99,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
private Button mDoneButton;
private Button mEditButton;
private ImageButton mFullScreenButton;
- private int mLastSelectedButtonIndex = MagnificationSize.NONE;
+ private int mLastSelectedButtonIndex = MagnificationSize.DEFAULT;
private boolean mAllowDiagonalScrolling = false;
@@ -115,19 +116,21 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
@Retention(RetentionPolicy.SOURCE)
@IntDef({
- MagnificationSize.NONE,
+ MagnificationSize.CUSTOM,
MagnificationSize.SMALL,
MagnificationSize.MEDIUM,
MagnificationSize.LARGE,
- MagnificationSize.FULLSCREEN
+ MagnificationSize.FULLSCREEN,
+ MagnificationSize.DEFAULT
})
/** Denotes the Magnification size type. */
public @interface MagnificationSize {
- int NONE = 0;
+ int CUSTOM = 0;
int SMALL = 1;
int MEDIUM = 2;
int LARGE = 3;
int FULLSCREEN = 4;
+ int DEFAULT = MEDIUM;
}
@VisibleForTesting
@@ -445,13 +448,20 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
private void updateUIControlsIfNeeded() {
int capability = getMagnificationCapability();
int selectedButtonIndex = mLastSelectedButtonIndex;
+ WindowMagnificationFrameSizePrefs windowMagnificationFrameSizePrefs =
+ new WindowMagnificationFrameSizePrefs(mContext);
switch (capability) {
case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
mEditButton.setVisibility(View.VISIBLE);
mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
mFullScreenButton.setVisibility(View.GONE);
if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
- selectedButtonIndex = MagnificationSize.NONE;
+ if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+ selectedButtonIndex =
+ windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
+ } else {
+ selectedButtonIndex = MagnificationSize.CUSTOM;
+ }
}
break;
@@ -613,7 +623,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
public void editMagnifierSizeMode(boolean enable) {
setEditMagnifierSizeMode(enable);
- updateSelectedButton(MagnificationSize.NONE);
+ updateSelectedButton(MagnificationSize.CUSTOM);
hideSettingPanel();
}
@@ -621,7 +631,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
if (index == MagnificationSize.FULLSCREEN) {
// transit to fullscreen magnifier if needed
transitToMagnificationMode(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
- } else if (index != MagnificationSize.NONE) {
+ } else if (index != MagnificationSize.CUSTOM) {
// update the window magnifier size
mCallback.onSetMagnifierSize(index);
// transit to window magnifier if needed
@@ -706,7 +716,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
});
}
- private void updateSelectedButton(@MagnificationSize int index) {
+ void updateSelectedButton(@MagnificationSize int index) {
// Clear the state of last selected button
if (mLastSelectedButtonIndex == MagnificationSize.SMALL) {
mSmallButton.setSelected(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index a25e9a20f81c..b4a248239f46 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility;
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
import android.graphics.Rect;
/**
@@ -68,4 +70,9 @@ interface WindowMagnifierCallback {
* @param displayId The logical display id.
*/
void onClickSettingsButton(int displayId);
+
+ /**
+ * Called when restoring the magnification window size.
+ */
+ void onWindowMagnifierBoundsRestored(int displayId, @MagnificationSize int index);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index f1fb45c9b16e..baca9594dd2f 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -79,7 +79,8 @@ public class ShadeTouchHandler implements TouchHandler {
if (mCapture != null && mCapture) {
sendTouchEvent((MotionEvent) ev);
}
- if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+ if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
+ || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
session.pop();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 4035e957a2db..efa55e90081e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -128,8 +128,13 @@ public class TouchMonitor {
completer.set(predecessor);
}
- if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) {
- stopMonitoring(false);
+ if (mActiveTouchSessions.isEmpty()) {
+ if (mStopMonitoringPending) {
+ stopMonitoring(false);
+ } else {
+ // restart monitoring to reset any destructive state on the input session
+ startMonitoring();
+ }
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 5c53234f74eb..e63472684585 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -88,7 +88,6 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
private boolean mPowerSaveEnabled;
private boolean mIsBatteryDefender;
private boolean mIsIncompatibleCharging;
- private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
// Lazily-loaded since this is expected to be a rare-if-ever state
@@ -270,7 +269,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
int resId = 0;
if (mPowerSaveEnabled) {
resId = R.drawable.battery_unified_attr_powersave;
- } else if (mIsBatteryDefender && mDisplayShieldEnabled) {
+ } else if (mIsBatteryDefender) {
resId = R.drawable.battery_unified_attr_defend;
} else if (isCharging) {
resId = R.drawable.battery_unified_attr_charging;
@@ -288,7 +287,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
private ColorProfile getCurrentColorProfile() {
return getColorProfile(
mPowerSaveEnabled,
- mIsBatteryDefender && mDisplayShieldEnabled,
+ mIsBatteryDefender,
mPluggedIn,
mLevel <= 20);
}
@@ -410,10 +409,6 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
mBatteryEstimateFetcher = fetcher;
}
- void setDisplayShieldEnabled(boolean displayShieldEnabled) {
- mDisplayShieldEnabled = displayShieldEnabled;
- }
-
void updatePercentText() {
if (!newStatusBarIcons()) {
updatePercentTextLegacy();
@@ -659,7 +654,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
float mainBatteryWidth =
res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
- boolean displayShield = mDisplayShieldEnabled && mIsBatteryDefender;
+ boolean displayShield = mIsBatteryDefender;
float fullBatteryIconHeight =
BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
float fullBatteryIconWidth =
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 4f13e6ffd0b5..9a30c213a2f9 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -34,7 +34,6 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
@@ -153,8 +152,6 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView>
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
- mView.setDisplayShieldEnabled(
- getContext().getResources().getBoolean(R.bool.flag_battery_shield_icon));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
mSettingObserver = new SettingObserver(mMainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 85e2bdb43ba5..b6ace81d18ba 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -19,10 +19,14 @@ package com.android.systemui.common.ui.view
import android.annotation.SuppressLint
import android.content.Context
+import android.os.Bundle
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.android.systemui.shade.TouchLogger
import kotlin.math.pow
import kotlin.math.sqrt
@@ -44,6 +48,10 @@ class LongPressHandlingView(
attrs,
) {
+ init {
+ setupAccessibilityDelegate()
+ }
+
constructor(
context: Context,
attrs: AttributeSet?,
@@ -55,6 +63,7 @@ class LongPressHandlingView(
view: View,
x: Int,
y: Int,
+ isA11yAction: Boolean = false,
)
/** Notifies that the gesture was too short for a long press, it is actually a click. */
@@ -63,6 +72,8 @@ class LongPressHandlingView(
var listener: Listener? = null
+ var accessibilityHintLongPressAction: AccessibilityAction? = null
+
private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
LongPressHandlingViewInteractionHandler(
postDelayed = { block, timeoutMs ->
@@ -107,6 +118,51 @@ class LongPressHandlingView(
override fun onTouchEvent(event: MotionEvent?): Boolean {
return interactionHandler.onTouchEvent(event?.toModel())
}
+
+ private fun setupAccessibilityDelegate() {
+ accessibilityDelegate =
+ object : AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ v: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(v, info)
+ if (
+ interactionHandler.isLongPressHandlingEnabled &&
+ accessibilityHintLongPressAction != null
+ ) {
+ info.addAction(accessibilityHintLongPressAction)
+ }
+ }
+
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ return if (
+ interactionHandler.isLongPressHandlingEnabled &&
+ action == AccessibilityNodeInfoCompat.ACTION_LONG_CLICK
+ ) {
+ val longPressHandlingView = host as? LongPressHandlingView
+ if (longPressHandlingView != null) {
+ // the coordinates are not available as it is an a11y long press
+ listener?.onLongPressDetected(
+ view = longPressHandlingView,
+ x = 0,
+ y = 0,
+ isA11yAction = true,
+ )
+ true
+ } else {
+ false
+ }
+ } else {
+ super.performAccessibilityAction(host, action, args)
+ }
+ }
+ }
+ }
}
private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 3d201a36417b..a44533555a04 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.dagger
import android.content.Context
+import com.android.systemui.CoreStartable
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalDatabaseModule
import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
@@ -26,6 +27,7 @@ import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryM
import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.communal.util.CommunalColorsImpl
@@ -40,6 +42,8 @@ import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import kotlinx.coroutines.CoroutineScope
@Module(
@@ -69,6 +73,13 @@ interface CommunalModule {
@Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalSceneTransitionInteractor::class)
+ abstract fun bindCommunalSceneTransitionInteractor(
+ impl: CommunalSceneTransitionInteractor
+ ): CoreStartable
+
companion object {
@Provides
@Communal
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index d8067b887c67..4de39c457f3b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -49,14 +49,8 @@ interface CommunalPrefsRepository {
/** Whether the CTA tile has been dismissed. */
fun isCtaDismissed(user: UserInfo): Flow<Boolean>
- /** Whether the lock screen widget disclaimer has been dismissed by the user. */
- fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean>
-
/** Save the CTA tile dismissed state for the current user. */
suspend fun setCtaDismissed(user: UserInfo)
-
- /** Save the lock screen widget disclaimer dismissed state for the current user. */
- suspend fun setDisclaimerDismissed(user: UserInfo)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -74,9 +68,6 @@ constructor(
override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
readKeyForUser(user, CTA_DISMISSED_STATE)
- override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> =
- readKeyForUser(user, DISCLAIMER_DISMISSED_STATE)
-
/**
* Emits an event each time a Backup & Restore restoration job is completed, and once at the
* start of collection.
@@ -97,12 +88,6 @@ constructor(
logger.i("Dismissed CTA tile")
}
- override suspend fun setDisclaimerDismissed(user: UserInfo) =
- withContext(bgDispatcher) {
- getSharedPrefsForUser(user).edit().putBoolean(DISCLAIMER_DISMISSED_STATE, true).apply()
- logger.i("Dismissed widget disclaimer")
- }
-
private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
return userFileManager.getSharedPreferences(
FILE_NAME,
@@ -124,6 +109,5 @@ constructor(
const val TAG = "CommunalPrefsRepository"
const val FILE_NAME = "communal_hub_prefs"
const val CTA_DISMISSED_STATE = "cta_dismissed"
- const val DISCLAIMER_DISMISSED_STATE = "disclaimer_dismissed"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
index 7a4006d515f7..260dcbad6201 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
@@ -28,7 +28,6 @@ import com.android.systemui.scene.shared.model.SceneDataSource
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -52,7 +51,7 @@ interface CommunalSceneRepository {
fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null)
/** Immediately snaps to the desired scene. */
- fun snapToScene(toScene: SceneKey, delayMillis: Long = 0)
+ fun snapToScene(toScene: SceneKey)
/**
* Updates the transition state of the hub [SceneTransitionLayout].
@@ -93,11 +92,10 @@ constructor(
}
}
- override fun snapToScene(toScene: SceneKey, delayMillis: Long) {
+ override fun snapToScene(toScene: SceneKey) {
applicationScope.launch {
// SceneTransitionLayout state updates must be triggered on the thread the STL was
// created on.
- delay(delayMillis)
sceneDataSource.snapToScene(toScene)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt
new file mode 100644
index 000000000000..7d9e1df41e6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.systemui.communal.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class CommunalSceneTransitionRepository @Inject constructor() {
+ /**
+ * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the
+ * next transition away from communal scene is started. It will be consumed exactly once and
+ * after that the state will be set back to null.
+ */
+ val nextLockscreenTargetState: MutableStateFlow<KeyguardState?> = MutableStateFlow(null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 3fffd76ab6a9..dbddc23d6146 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -23,6 +23,7 @@ import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
+import com.android.app.tracing.coroutines.launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
@@ -64,10 +65,12 @@ import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
+import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -94,6 +97,7 @@ class CommunalInteractor
@Inject
constructor(
@Application val applicationScope: CoroutineScope,
+ @Background private val bgScope: CoroutineScope,
@Background val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
private val widgetRepository: CommunalWidgetRepository,
@@ -117,9 +121,25 @@ constructor(
private val _editModeOpen = MutableStateFlow(false)
- /** Whether edit mode is currently open. */
+ /**
+ * Whether edit mode is currently open. This will be true from onCreate to onDestroy in
+ * [EditWidgetsActivity] and thus does not correspond to whether or not the activity is visible.
+ *
+ * Note that since this is called in onDestroy, it's not guaranteed to ever be set to false when
+ * edit mode is closed, such as in the case that a user exits edit mode manually with a back
+ * gesture or navigation gesture.
+ */
val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
+ private val _editActivityShowing = MutableStateFlow(false)
+
+ /**
+ * Whether the edit mode activity is currently showing. This is true from onStart to onStop in
+ * [EditWidgetsActivity] so may be false even when the user is in edit mode, such as when a
+ * widget's individual configuration activity has launched.
+ */
+ val editActivityShowing: StateFlow<Boolean> = _editActivityShowing.asStateFlow()
+
/** Whether communal features are enabled. */
val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
@@ -148,6 +168,17 @@ constructor(
replay = 1,
)
+ private val _isDisclaimerDismissed = MutableStateFlow(false)
+ val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
+
+ fun setDisclaimerDismissed() {
+ bgScope.launch("$TAG#setDisclaimerDismissed") {
+ _isDisclaimerDismissed.value = true
+ delay(DISCLAIMER_RESET_MILLIS)
+ _isDisclaimerDismissed.value = false
+ }
+ }
+
/** Whether to show communal when exiting the occluded state. */
val showCommunalFromOccluded: Flow<Boolean> =
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -301,6 +332,10 @@ constructor(
_editModeOpen.value = isOpen
}
+ fun setEditActivityShowing(isOpen: Boolean) {
+ _editActivityShowing.value = isOpen
+ }
+
/** Show the widget editor Activity. */
fun showWidgetEditor(
preselectedKey: String? = null,
@@ -510,6 +545,14 @@ constructor(
}
companion object {
+ const val TAG = "CommunalInteractor"
+
+ /**
+ * The amount of time between showing the widget disclaimer to the user as measured from the
+ * moment the disclaimer is dimsissed.
+ */
+ val DISCLAIMER_RESET_MILLIS = 30.minutes
+
/**
* The user activity timeout which should be used when the communal hub is opened. A value
* of -1 means that the user's chosen screen timeout will be used instead.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
index 3517650c8cf3..0b5f40d8041e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal.domain.interactor
import android.content.pm.UserInfo
-import com.android.app.tracing.coroutines.launch
import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -43,7 +42,7 @@ constructor(
private val repository: CommunalPrefsRepository,
userInteractor: SelectedUserInteractor,
private val userTracker: UserTracker,
- @CommunalTableLog tableLogBuffer: TableLogBuffer
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
val isCtaDismissed: Flow<Boolean> =
@@ -64,25 +63,6 @@ constructor(
suspend fun setCtaDismissed(user: UserInfo = userTracker.userInfo) =
repository.setCtaDismissed(user)
- val isDisclaimerDismissed: Flow<Boolean> =
- userInteractor.selectedUserInfo
- .flatMapLatest { user -> repository.isDisclaimerDismissed(user) }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnPrefix = "",
- columnName = "isDisclaimerDismissed",
- initialValue = false,
- )
- .stateIn(
- scope = bgScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
- fun setDisclaimerDismissed(user: UserInfo = userTracker.userInfo) {
- bgScope.launch("$TAG#setDisclaimerDismissed") { repository.setDisclaimerDismissed(user) }
- }
-
private companion object {
const val TAG = "CommunalPrefsInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 122f964713a9..45cfe3673f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.domain.interactor
+import com.android.app.tracing.coroutines.launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
@@ -26,9 +27,11 @@ import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.KeyguardState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -39,6 +42,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -48,7 +52,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val communalSceneRepository: CommunalSceneRepository,
) {
- val _isLaunchingWidget = MutableStateFlow(false)
+ private val _isLaunchingWidget = MutableStateFlow(false)
/** Whether a widget launch is currently in progress. */
val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow()
@@ -57,17 +61,48 @@ constructor(
_isLaunchingWidget.value = launching
}
+ fun interface OnSceneAboutToChangeListener {
+ /** Notifies that the scene is about to change to [toScene]. */
+ fun onSceneAboutToChange(toScene: SceneKey, keyguardState: KeyguardState?)
+ }
+
+ private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>()
+
+ /** Registers a listener which is called when the scene is about to change. */
+ fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) {
+ onSceneAboutToChangeListener.add(processor)
+ }
+
/**
* Asks for an asynchronous scene witch to [newScene], which will use the corresponding
* installed transition or the one specified by [transitionKey], if provided.
*/
- fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) {
- communalSceneRepository.changeScene(newScene, transitionKey)
+ fun changeScene(
+ newScene: SceneKey,
+ transitionKey: TransitionKey? = null,
+ keyguardState: KeyguardState? = null,
+ ) {
+ applicationScope.launch {
+ notifyListeners(newScene, keyguardState)
+ communalSceneRepository.changeScene(newScene, transitionKey)
+ }
}
/** Immediately snaps to the new scene. */
- fun snapToScene(newScene: SceneKey, delayMillis: Long = 0) {
- communalSceneRepository.snapToScene(newScene, delayMillis)
+ fun snapToScene(
+ newScene: SceneKey,
+ delayMillis: Long = 0,
+ keyguardState: KeyguardState? = null
+ ) {
+ applicationScope.launch("$TAG#snapToScene") {
+ delay(delayMillis)
+ notifyListeners(newScene, keyguardState)
+ communalSceneRepository.snapToScene(newScene)
+ }
+ }
+
+ private fun notifyListeners(newScene: SceneKey, keyguardState: KeyguardState?) {
+ onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(newScene, keyguardState) }
}
/** Changes to Blank scene when starting an activity after dismissing keyguard. */
@@ -164,4 +199,8 @@ constructor(
started = SharingStarted.WhileSubscribed(),
initialValue = false,
)
+
+ private companion object {
+ const val TAG = "CommunalSceneInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
new file mode 100644
index 000000000000..8351566fcae6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2024 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.systemui.communal.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.data.repository.CommunalSceneTransitionRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.InternalKeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * This class listens to [SceneTransitionLayout] transitions and manages keyguard transition
+ * framework (KTF) states accordingly for communal states.
+ *
+ * There are a few rules:
+ * - There are only 2 communal scenes: [CommunalScenes.Communal] and [CommunalScenes.Blank]
+ * - When scene framework is on [CommunalScenes.Blank], KTF is allowed to change its scenes freely
+ * - When scene framework is on [CommunalScenes.Communal], KTF is locked into
+ * [KeyguardState.GLANCEABLE_HUB]
+ */
+@SysUISingleton
+class CommunalSceneTransitionInteractor
+@Inject
+constructor(
+ val transitionInteractor: KeyguardTransitionInteractor,
+ val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
+ private val settingsInteractor: CommunalSettingsInteractor,
+ @Application private val applicationScope: CoroutineScope,
+ private val sceneInteractor: CommunalSceneInteractor,
+ private val repository: CommunalSceneTransitionRepository,
+ keyguardInteractor: KeyguardInteractor,
+) : CoreStartable, CommunalSceneInteractor.OnSceneAboutToChangeListener {
+
+ private var currentTransitionId: UUID? = null
+ private var progressJob: Job? = null
+
+ private val currentToState: KeyguardState
+ get() = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+
+ /**
+ * The next keyguard state to trigger when exiting [CommunalScenes.Communal]. This is only used
+ * if the state is changed by user gesture or not explicitly defined by the caller when changing
+ * scenes programmatically.
+ *
+ * This is needed because we do not always want to exit back to the KTF state we came from. For
+ * example, when going from HUB (Communal) -> OCCLUDED (Blank) -> HUB (Communal) and then
+ * closing the hub via gesture, we don't want to go back to OCCLUDED but instead either go to
+ * DREAM or LOCKSCREEN depending on if there is a dream showing.
+ */
+ private val nextKeyguardStateInternal =
+ combine(
+ keyguardInteractor.isDreaming,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isKeyguardGoingAway,
+ ) { dreaming, occluded, keyguardGoingAway ->
+ if (keyguardGoingAway) {
+ KeyguardState.GONE
+ } else if (dreaming) {
+ KeyguardState.DREAMING
+ } else if (occluded) {
+ KeyguardState.OCCLUDED
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ }
+
+ private val nextKeyguardState: StateFlow<KeyguardState> =
+ combine(
+ repository.nextLockscreenTargetState,
+ nextKeyguardStateInternal,
+ ) { override, nextState ->
+ override ?: nextState
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = KeyguardState.LOCKSCREEN,
+ )
+
+ override fun start() {
+ if (
+ communalSceneKtfRefactor() &&
+ settingsInteractor.isCommunalFlagEnabled() &&
+ !SceneContainerFlag.isEnabled
+ ) {
+ sceneInteractor.registerSceneStateProcessor(this)
+ listenForSceneTransitionProgress()
+ }
+ }
+
+ /**
+ * Called when the scene is programmatically changed, allowing callers to specify which KTF
+ * state should be set when transitioning to [CommunalScenes.Blank]
+ */
+ override fun onSceneAboutToChange(toScene: SceneKey, keyguardState: KeyguardState?) {
+ if (toScene != CommunalScenes.Blank || keyguardState == null) return
+ repository.nextLockscreenTargetState.value = keyguardState
+ }
+
+ /** Monitors [SceneTransitionLayout] state and updates KTF state accordingly. */
+ private fun listenForSceneTransitionProgress() {
+ applicationScope.launch {
+ sceneInteractor.transitionState
+ .pairwise(ObservableTransitionState.Idle(CommunalScenes.Blank))
+ .collect { (prevTransition, transition) ->
+ when (transition) {
+ is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition)
+ is ObservableTransitionState.Transition ->
+ handleTransition(prevTransition, transition)
+ }
+ }
+ }
+ }
+
+ private suspend fun handleIdle(
+ prevTransition: ObservableTransitionState,
+ idle: ObservableTransitionState.Idle
+ ) {
+ if (
+ prevTransition is ObservableTransitionState.Transition &&
+ currentTransitionId != null &&
+ idle.currentScene == prevTransition.toScene
+ ) {
+ finishCurrentTransition()
+ } else {
+ // We may receive an Idle event without a corresponding Transition
+ // event, such as when snapping to a scene without an animation.
+ val targetState =
+ if (idle.currentScene == CommunalScenes.Blank) {
+ nextKeyguardState.value
+ } else {
+ KeyguardState.GLANCEABLE_HUB
+ }
+ transitionKtfTo(targetState)
+ repository.nextLockscreenTargetState.value = null
+ }
+ }
+
+ private fun finishCurrentTransition() {
+ internalTransitionInteractor.updateTransition(
+ currentTransitionId!!,
+ 1f,
+ TransitionState.FINISHED
+ )
+ resetTransitionData()
+ }
+
+ private suspend fun finishReversedTransitionTo(state: KeyguardState) {
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
+ to = state,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE
+ )
+ currentTransitionId = internalTransitionInteractor.startTransition(newTransition)
+ internalTransitionInteractor.updateTransition(
+ currentTransitionId!!,
+ 1f,
+ TransitionState.FINISHED
+ )
+ resetTransitionData()
+ }
+
+ private fun resetTransitionData() {
+ progressJob?.cancel()
+ progressJob = null
+ currentTransitionId = null
+ }
+
+ private suspend fun handleTransition(
+ prevTransition: ObservableTransitionState,
+ transition: ObservableTransitionState.Transition
+ ) {
+ if (prevTransition.isTransitioning(from = transition.fromScene, to = transition.toScene)) {
+ // This is a new transition, but exactly the same as the previous state. Skip resetting
+ // KTF for this case and just collect the new progress instead.
+ collectProgress(transition)
+ } else if (transition.toScene == CommunalScenes.Communal) {
+ if (currentTransitionId != null) {
+ if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+ transitionKtfTo(transitionInteractor.getStartedFromState())
+ }
+ }
+ startTransitionToGlanceableHub()
+ collectProgress(transition)
+ } else if (transition.toScene == CommunalScenes.Blank) {
+ if (currentTransitionId != null) {
+ // Another transition started before this one is completed. Transition to the
+ // GLANCEABLE_HUB state so that we can properly transition away from it.
+ transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
+ }
+ startTransitionFromGlanceableHub()
+ collectProgress(transition)
+ }
+ }
+
+ private suspend fun transitionKtfTo(state: KeyguardState) {
+ val currentTransition = transitionInteractor.transitionState.value
+ if (currentTransition.isFinishedIn(state)) {
+ // This is already the state we want to be in
+ resetTransitionData()
+ } else if (currentTransition.isTransitioning(to = state)) {
+ finishCurrentTransition()
+ } else {
+ finishReversedTransitionTo(state)
+ }
+ }
+
+ private fun collectProgress(transition: ObservableTransitionState.Transition) {
+ progressJob?.cancel()
+ progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } }
+ }
+
+ private suspend fun startTransitionFromGlanceableHub() {
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = nextKeyguardState.value,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ repository.nextLockscreenTargetState.value = null
+ startTransition(newTransition)
+ }
+
+ private suspend fun startTransitionToGlanceableHub() {
+ val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = currentState,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ startTransition(newTransition)
+ }
+
+ private suspend fun startTransition(transitionInfo: TransitionInfo) {
+ if (currentTransitionId != null) {
+ resetTransitionData()
+ }
+ currentTransitionId = internalTransitionInteractor.startTransition(transitionInfo)
+ }
+
+ private fun updateProgress(progress: Float) {
+ if (currentTransitionId == null) return
+ internalTransitionInteractor.updateTransition(
+ currentTransitionId!!,
+ progress.coerceIn(0f, 1f),
+ TransitionState.RUNNING
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 19d7ceba2310..01ed2b71bea0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.ui.view.MediaHost
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -75,8 +76,16 @@ abstract class BaseCommunalViewModel(
communalInteractor.signalUserInteraction()
}
- fun changeScene(scene: SceneKey, transitionKey: TransitionKey? = null) {
- communalSceneInteractor.changeScene(scene, transitionKey)
+ /**
+ * Asks for an asynchronous scene witch to [newScene], which will use the corresponding
+ * installed transition or the one specified by [transitionKey], if provided.
+ */
+ fun changeScene(
+ scene: SceneKey,
+ transitionKey: TransitionKey? = null,
+ keyguardState: KeyguardState? = null
+ ) {
+ communalSceneInteractor.changeScene(scene, transitionKey, keyguardState)
}
fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 7b0aadfdcebd..830f543fd06f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -24,7 +24,6 @@ import android.content.res.Resources
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.Flags.enableWidgetPickerSizeFilter
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
@@ -82,10 +81,10 @@ constructor(
communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING }
val showDisclaimer: Flow<Boolean> =
- allOf(isCommunalContentVisible, not(communalPrefsInteractor.isDisclaimerDismissed))
+ allOf(isCommunalContentVisible, not(communalInteractor.isDisclaimerDismissed))
fun onDisclaimerDismissed() {
- communalPrefsInteractor.setDisclaimerDismissed()
+ communalInteractor.setDisclaimerDismissed()
}
/**
@@ -176,16 +175,14 @@ constructor(
return Intent(Intent.ACTION_PICK).apply {
setPackage(packageName)
- if (enableWidgetPickerSizeFilter()) {
- putExtra(
- EXTRA_DESIRED_WIDGET_WIDTH,
- resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
- )
- putExtra(
- EXTRA_DESIRED_WIDGET_HEIGHT,
- resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
- )
- }
+ putExtra(
+ EXTRA_DESIRED_WIDGET_WIDTH,
+ resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
+ )
+ putExtra(
+ EXTRA_DESIRED_WIDGET_HEIGHT,
+ resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
+ )
putExtra(
AppWidgetManager.EXTRA_CATEGORY_FILTER,
CommunalWidgetCategories.defaultCategories
@@ -217,6 +214,14 @@ constructor(
/** Sets whether edit mode is currently open */
fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
+ /**
+ * Sets whether the edit mode activity is currently showing.
+ *
+ * See [CommunalInteractor.editActivityShowing] for more info.
+ */
+ fun setEditActivityShowing(showing: Boolean) =
+ communalInteractor.setEditActivityShowing(showing)
+
/** Called when exiting the edit mode, before transitioning back to the communal scene. */
fun cleanupEditModeState() {
communalSceneInteractor.setEditModeState(null)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
index 4efaf878f33f..08444623f24d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -37,13 +37,21 @@ class CommunalTransitionAnimatorController(
delegate.onIntentStarted(willAnimate)
}
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+ // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay.
+ communalSceneInteractor.snapToScene(
+ CommunalScenes.Blank,
+ ActivityTransitionAnimator.TIMINGS.totalDuration
+ )
+ }
+
override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
communalSceneInteractor.setIsLaunchingWidget(false)
delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- communalSceneInteractor.snapToScene(CommunalScenes.Blank)
communalSceneInteractor.setIsLaunchingWidget(false)
delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 46f802fd2bce..08fe42ede5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -96,8 +96,7 @@ constructor(
run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
}
}
- }
- ?: run { Log.w(TAG, "No data in result.") }
+ } ?: run { Log.w(TAG, "No data in result.") }
}
else ->
Log.w(
@@ -195,6 +194,8 @@ constructor(
override fun onStart() {
super.onStart()
+ communalViewModel.setEditActivityShowing(true)
+
if (shouldOpenWidgetPickerOnStart) {
onOpenWidgetPicker()
shouldOpenWidgetPickerOnStart = false
@@ -206,6 +207,7 @@ constructor(
override fun onStop() {
super.onStop()
+ communalViewModel.setEditActivityShowing(false)
logger.i("Stopping the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 7f8103e63684..6864f4e96994 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,7 +18,7 @@ package com.android.systemui.controls.ui
import android.app.Activity
import android.app.ActivityOptions
-import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
import android.app.Dialog
import android.app.PendingIntent
import android.content.ComponentName
@@ -93,8 +93,8 @@ class DetailDialog(
0 /* enterResId */,
0 /* exitResId */
).apply {
- pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ pendingIntentBackgroundActivityStartMode =
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
taskAlwaysOnTop = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 3294c816d67c..b45ebd865c55 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -256,6 +256,7 @@ constructor(
it.cancel()
null
}
+ mOverlayStateController.setExitAnimationsRunning(false)
}
private fun blurAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 53b9261991e0..0e2e2e6277f2 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -16,12 +16,21 @@
package com.android.systemui.education.dagger
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
+import com.android.systemui.shared.education.GestureType
import dagger.Binds
+import dagger.Lazy
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import java.time.Clock
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
@@ -53,5 +62,41 @@ interface ContextualEducationModule {
fun provideEduClock(): Clock {
return Clock.systemUTC()
}
+
+ @Provides
+ @IntoMap
+ @ClassKey(ContextualEducationInteractor::class)
+ fun provideContextualEducationInteractor(
+ implLazy: Lazy<ContextualEducationInteractor>
+ ): CoreStartable {
+ return if (Flags.keyboardTouchpadContextualEducation()) {
+ implLazy.get()
+ } else {
+ // No-op implementation when the flag is disabled.
+ return NoOpCoreStartable
+ }
+ }
+
+ @Provides
+ fun provideKeyboardTouchpadEduStatsInteractor(
+ implLazy: Lazy<KeyboardTouchpadEduStatsInteractorImpl>
+ ): KeyboardTouchpadEduStatsInteractor {
+ return if (Flags.keyboardTouchpadContextualEducation()) {
+ implLazy.get()
+ } else {
+ // No-op implementation when the flag is disabled.
+ return NoOpKeyboardTouchpadEduStatsInteractor
+ }
+ }
+ }
+
+ private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
+ override fun incrementSignalCount(gestureType: GestureType) {}
+
+ override fun updateShortcutTriggerTime(gestureType: GestureType) {}
+ }
+
+ private object NoOpCoreStartable : CoreStartable {
+ override fun start() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
new file mode 100644
index 000000000000..e2aa9111246b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 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.systemui.education.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.repository.ContextualEducationRepository
+import com.android.systemui.shared.education.GestureType
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Allows updating education data (e.g. signal count, shortcut time) for different gesture types.
+ * Change user education repository when user is changed.
+ */
+@SysUISingleton
+class ContextualEducationInteractor
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val repository: ContextualEducationRepository,
+) : CoreStartable {
+
+ override fun start() {
+ backgroundScope.launch {
+ selectedUserInteractor.selectedUser.collectLatest { repository.setUser(it) }
+ }
+ }
+
+ suspend fun incrementSignalCount(gestureType: GestureType) =
+ repository.incrementSignalCount(gestureType)
+
+ suspend fun updateShortcutTriggerTime(gestureType: GestureType) =
+ repository.updateShortcutTriggerTime(gestureType)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
new file mode 100644
index 000000000000..643e571d2927
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 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.systemui.education.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.education.GestureType
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates the update functions of KeyboardTouchpadEduStatsInteractor. This encapsulation is
+ * for having a different implementation of interactor when the feature flag is off.
+ */
+interface KeyboardTouchpadEduStatsInteractor {
+ fun incrementSignalCount(gestureType: GestureType)
+
+ fun updateShortcutTriggerTime(gestureType: GestureType)
+}
+
+/** Allow update to education data related to keyboard/touchpad. */
+@SysUISingleton
+class KeyboardTouchpadEduStatsInteractorImpl
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val contextualEducationInteractor: ContextualEducationInteractor
+) : KeyboardTouchpadEduStatsInteractor {
+
+ override fun incrementSignalCount(gestureType: GestureType) {
+ // Todo: check if keyboard/touchpad is connected before update
+ backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
+ }
+
+ override fun updateShortcutTriggerTime(gestureType: GestureType) {
+ backgroundScope.launch {
+ contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 1ba274ff4e76..0e06117b2693 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -37,6 +37,8 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
@@ -55,6 +57,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
+ NotificationMinimalismPrototype.token dependsOn NotificationsHeadsUpRefactor.token
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3d3584e29def..d0beb7abc25b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -137,12 +137,6 @@ object Flags {
// TODO(b/267722622): Tracking Bug
@JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
- /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
- // TODO(b/277220285): Tracking bug.
- @JvmField
- val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
- unreleasedFlag("lock_screen_long_press_directly_opens_wallpaper_picker")
-
/** Whether page transition animations in the wallpaper picker are enabled */
// TODO(b/291710220): Tracking bug.
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index c44eb471d460..491c73d0ae56 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -16,9 +16,15 @@
package com.android.systemui.haptics.qs
+import android.content.ComponentName
import android.os.VibrationEffect
import android.service.quicksettings.Tile
+import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
@@ -58,6 +64,7 @@ constructor(
/** The [QSTile] and [Expandable] used to perform a long-click and click actions */
var qsTile: QSTile? = null
var expandable: Expandable? = null
+ private set
/** Haptic effects */
private val durations =
@@ -125,8 +132,10 @@ constructor(
}
fun handleAnimationStart() {
- vibrate(longPressHint)
- setState(State.RUNNING_FORWARD)
+ if (state == State.TIMEOUT_WAIT) {
+ vibrate(longPressHint)
+ setState(State.RUNNING_FORWARD)
+ }
}
/** This function is called both when an animator completes or gets cancelled */
@@ -147,7 +156,10 @@ constructor(
setState(getStateForClick())
qsTile?.click(expandable)
}
- State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
+ State.RUNNING_BACKWARDS_FROM_CANCEL -> {
+ callback?.onEffectFinishedReversing()
+ setState(State.IDLE)
+ }
else -> {}
}
}
@@ -222,13 +234,58 @@ constructor(
fun resetState() = setState(State.IDLE)
+ fun createExpandableFromView(view: View) {
+ expandable =
+ object : Expandable {
+ override fun activityTransitionController(
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ component: ComponentName?,
+ returnCujType: Int?,
+ ): ActivityTransitionAnimator.Controller? {
+ val delegatedController =
+ ActivityTransitionAnimator.Controller.fromView(
+ view,
+ launchCujType,
+ cookie,
+ component,
+ returnCujType,
+ )
+ return delegatedController?.let { createTransitionControllerDelegate(it) }
+ }
+
+ override fun dialogTransitionController(
+ cuj: DialogCuj?,
+ ): DialogTransitionAnimator.Controller? =
+ DialogTransitionAnimator.Controller.fromView(view, cuj)
+ }
+ }
+
+ @VisibleForTesting
+ fun createTransitionControllerDelegate(
+ controller: ActivityTransitionAnimator.Controller
+ ): DelegateTransitionAnimatorController {
+ val delegated =
+ object : DelegateTransitionAnimatorController(controller) {
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ if (state == State.LONG_CLICKED) {
+ setState(State.RUNNING_BACKWARDS_FROM_CANCEL)
+ callback?.onReverseAnimator(false)
+ }
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+ }
+ return delegated
+ }
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
RUNNING_FORWARD, /* The effect is running normally */
/* The effect was interrupted by an ACTION_UP and is now running backwards */
RUNNING_BACKWARDS_FROM_UP,
- /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
+ /* The effect was cancelled by an ACTION_CANCEL or a shade collapse and is now running
+ backwards */
RUNNING_BACKWARDS_FROM_CANCEL,
CLICKED, /* The effect has ended with a click */
LONG_CLICKED, /* The effect has ended with a long-click */
@@ -247,7 +304,7 @@ constructor(
fun onStartAnimator()
/** Reverse the effect animator */
- fun onReverseAnimator()
+ fun onReverseAnimator(playHaptics: Boolean = true)
/** Cancel the effect animator */
fun onCancelAnimator()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 893835a38c53..e5ccc4a14003 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import android.util.Log
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
@@ -118,7 +119,9 @@ constructor(
// needed. Also, don't react to wake and unlock events, as we'll be
// receiving a call to #dismissAod() shortly when the authentication
// completes.
- !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+ startTransitionTo(state, ownerReason = reason)
+ } &&
!isWakeAndUnlock(biometricUnlockState.mode) &&
!primaryBouncerShowing
} else {
@@ -184,11 +187,7 @@ constructor(
.filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
.collect {
if (!maybeHandleInsecurePowerGesture()) {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- ownerReason = "isOccluded = true",
- )
+ Log.i(TAG, "Ignoring change to isOccluded to prevent errant AOD->OCCLUDED")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 8f50b03eafec..8ef138eeb16a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -20,8 +20,11 @@ import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -166,7 +169,7 @@ constructor(
}
} else if (occluded) {
startTransitionTo(KeyguardState.OCCLUDED)
- } else if (isIdleOnCommunal) {
+ } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
if (SceneContainerFlag.isEnabled) {
// TODO(b/336576536): Check if adaptation for scene framework is needed
} else {
@@ -183,7 +186,7 @@ constructor(
if (SceneContainerFlag.isEnabled) {
// TODO(b/336576536): Check if adaptation for scene framework is needed
} else {
- startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ transitionToGlanceableHub()
}
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
@@ -218,7 +221,9 @@ constructor(
canWakeDirectlyToGone,
primaryBouncerShowing) ->
if (
- !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+ startTransitionTo(state, ownerReason = reason)
+ } &&
// Handled by dismissFromDozing().
!isWakeAndUnlock(biometricUnlockState.mode)
) {
@@ -242,7 +247,7 @@ constructor(
ownerReason = "waking from dozing"
)
}
- } else if (isIdleOnCommunal) {
+ } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
if (SceneContainerFlag.isEnabled) {
// TODO(b/336576536): Check if adaptation for scene framework is
// needed
@@ -264,10 +269,7 @@ constructor(
// TODO(b/336576536): Check if adaptation for scene framework is
// needed
} else {
- startTransitionTo(
- KeyguardState.GLANCEABLE_HUB,
- ownerReason = "waking from dozing"
- )
+ transitionToGlanceableHub()
}
} else {
startTransitionTo(
@@ -280,6 +282,18 @@ constructor(
}
}
+ private suspend fun transitionToGlanceableHub() {
+ if (communalSceneKtfRefactor()) {
+ communalSceneInteractor.changeScene(
+ CommunalScenes.Communal,
+ // Immediately show the hub when transitioning from dozing to hub.
+ CommunalTransitionKeys.Immediately,
+ )
+ } else {
+ startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ }
+ }
+
/** Dismisses keyguard from the DOZING state. */
fun dismissFromDozing() {
scope.launch { startTransitionTo(KeyguardState.GONE) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 453401d2aa7d..4c3a75e765b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -81,7 +82,9 @@ constructor(
listenForDreamingToLockscreenOrGone()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
- listenForDreamingToGlanceableHub()
+ if (!communalSceneKtfRefactor()) {
+ listenForDreamingToGlanceableHub()
+ }
listenForDreamingToPrimaryBouncer()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 1a7012abdbe7..d81195060071 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -19,7 +19,11 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -30,6 +34,7 @@ import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -50,6 +55,7 @@ constructor(
private val glanceableHubTransitions: GlanceableHubTransitions,
private val communalSettingsInteractor: CommunalSettingsInteractor,
keyguardInteractor: KeyguardInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
override val transitionRepository: KeyguardTransitionRepository,
override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
transitionInteractor: KeyguardTransitionInteractor,
@@ -72,7 +78,9 @@ constructor(
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
return
}
- listenForHubToLockscreenOrDreaming()
+ if (!communalSceneKtfRefactor()) {
+ listenForHubToLockscreenOrDreaming()
+ }
listenForHubToDozing()
listenForHubToPrimaryBouncer()
listenForHubToAlternateBouncer()
@@ -120,7 +128,10 @@ constructor(
scope.launch("$TAG#listenForHubToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
- .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
+ .collect {
+ // Bouncer shows on top of the hub, so do not change scenes here.
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
}
}
@@ -130,7 +141,10 @@ constructor(
.filterRelevantKeyguardStateAnd { alternateBouncerShowing ->
alternateBouncerShowing
}
- .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) }
+ .collect { pair ->
+ // Bouncer shows on top of the hub, so do not change scenes here.
+ startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
+ }
}
}
@@ -139,10 +153,18 @@ constructor(
powerInteractor.isAsleep
.filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
.collect {
- startTransitionTo(
- toState = KeyguardState.DOZING,
- modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
- )
+ if (communalSceneKtfRefactor()) {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ transitionKey = CommunalTransitionKeys.Immediately,
+ keyguardState = KeyguardState.DOZING,
+ )
+ } else {
+ startTransitionTo(
+ toState = KeyguardState.DOZING,
+ modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+ )
+ }
}
}
}
@@ -152,7 +174,44 @@ constructor(
scope.launch {
keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
.filterRelevantKeyguardStateAnd { onTop -> onTop }
- .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
+ .collect {
+ maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+ if (communalSceneKtfRefactor()) {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ keyguardState = state,
+ )
+ null
+ } else {
+ startTransitionTo(state, ownerReason = reason)
+ }
+ }
+ }
+ }
+ } else if (communalSceneKtfRefactor()) {
+ scope.launch {
+ allOf(
+ keyguardInteractor.isKeyguardOccluded,
+ noneOf(
+ // Dream is a special-case of occluded, so filter out the dreaming
+ // case here.
+ keyguardInteractor.isDreaming,
+ // When launching activities from widgets on the hub, we have a
+ // custom occlusion animation.
+ communalSceneInteractor.isLaunchingWidget,
+ ),
+ )
+ .filterRelevantKeyguardStateAnd { isOccludedAndNotDreamingNorLaunchingWidget ->
+ isOccludedAndNotDreamingNorLaunchingWidget
+ }
+ .collect { _ ->
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ keyguardState = KeyguardState.OCCLUDED,
+ )
+ }
}
} else {
scope.launch {
@@ -160,9 +219,7 @@ constructor(
.filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming ->
isOccludedAndNotDreaming
}
- .collect { isOccludedAndNotDreaming ->
- startTransitionTo(KeyguardState.OCCLUDED)
- }
+ .collect { _ -> startTransitionTo(KeyguardState.OCCLUDED) }
}
}
}
@@ -170,10 +227,33 @@ constructor(
private fun listenForHubToGone() {
// TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
- scope.launch {
- keyguardInteractor.isKeyguardGoingAway
- .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
- .collect { startTransitionTo(KeyguardState.GONE) }
+ if (communalSceneKtfRefactor()) {
+ scope.launch {
+ allOf(
+ keyguardInteractor.isKeyguardGoingAway,
+ // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
+ // state until after edit mode is ready to be shown.
+ noneOf(
+ // When launching activities from widgets on the hub, we wait to change
+ // scenes until the activity launch is complete.
+ communalSceneInteractor.isLaunchingWidget,
+ ),
+ )
+ .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
+ .collect {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ keyguardState = KeyguardState.GONE
+ )
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
+ .collect { startTransitionTo(KeyguardState.GONE) }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 5c7adf0c5a8a..16c014f451f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,6 +20,7 @@ import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -90,7 +91,9 @@ constructor(
listenForLockscreenToPrimaryBouncerDragging()
listenForLockscreenToAlternateBouncer()
listenForLockscreenTransitionToCamera()
- listenForLockscreenToGlanceableHub()
+ if (!communalSceneKtfRefactor()) {
+ listenForLockscreenToGlanceableHub()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index f3ca9df6491c..2f320409f231 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,8 +18,12 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.Flags.restartDreamOnUnocclude
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -49,6 +53,7 @@ constructor(
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
@@ -140,7 +145,14 @@ constructor(
} else if (isIdleOnCommunal || showCommunalFromOccluded) {
// TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
- startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ if (communalSceneKtfRefactor()) {
+ communalSceneInteractor.changeScene(
+ CommunalScenes.Communal,
+ CommunalTransitionKeys.SimpleFade
+ )
+ } else {
+ startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ }
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 24290881a0fb..6c89ce055470 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -113,10 +113,9 @@ constructor(
(isBouncerShowing, isAwake, isActiveDreamLockscreenHosted, isIdleOnCommunal)
->
if (
- !maybeStartTransitionToOccludedOrInsecureCamera() &&
- !isBouncerShowing &&
- isAwake &&
- !isActiveDreamLockscreenHosted
+ !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
+ startTransitionTo(state, ownerReason = reason)
+ } && !isBouncerShowing && isAwake && !isActiveDreamLockscreenHosted
) {
val toState =
if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ec03a6d8121f..046e79cd04c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -188,7 +188,7 @@ constructor(
* Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
* but not vice-versa.
*/
- val isDreaming: Flow<Boolean> = repository.isDreaming
+ val isDreaming: StateFlow<Boolean> = repository.isDreaming
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
@@ -205,7 +205,8 @@ constructor(
trySendWithFailureLogging(
cameraLaunchSourceIntToModel(source),
TAG,
- "updated onCameraLaunchGestureDetected")
+ "updated onCameraLaunchGestureDetected"
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index 7a06d2fe9254..cd49c6a4d2e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -129,13 +129,16 @@ constructor(
}
}
- /** Notifies that the user has long-pressed on the lock screen. */
- fun onLongPress() {
+ /** Notifies that the user has long-pressed on the lock screen.
+ *
+ * @param isA11yAction: Whether the action was performed as an a11y action
+ */
+ fun onLongPress(isA11yAction: Boolean = false) {
if (!isLongPressHandlingEnabled.value) {
return
}
- if (featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP)) {
+ if (isA11yAction) {
showSettings()
} else {
showMenu()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 89c717892d0a..d06ee645652c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -122,9 +122,14 @@ sealed class TransitionInteractor(
* SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
* gesture cases. If so, start the transition.
*
+ * @param startTransition A callback which is triggered to start the transition to the desired
+ * KeyguardState. Allows caller to hook into the transition start if needed.
+ *
* Returns true if a transition was started, false otherwise.
*/
- suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
+ suspend fun maybeStartTransitionToOccludedOrInsecureCamera(
+ startTransition: suspend (state: KeyguardState, reason: String) -> UUID?
+ ): Boolean {
// The refactor is required for the occlusion interactor to work.
KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
@@ -136,10 +141,7 @@ sealed class TransitionInteractor(
if (!maybeHandleInsecurePowerGesture()) {
// Otherwise, the double tap gesture occurred while not GONE and not dismissable,
// which means we will launch the secure camera, which OCCLUDES the keyguard.
- startTransitionTo(
- KeyguardState.OCCLUDED,
- ownerReason = "Power button gesture on lockscreen"
- )
+ startTransition(KeyguardState.OCCLUDED, "Power button gesture on lockscreen")
}
return true
@@ -147,10 +149,7 @@ sealed class TransitionInteractor(
// A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
// it's visible.
// TODO(b/307976454) - Centralize transition to DREAMING here.
- startTransitionTo(
- KeyguardState.OCCLUDED,
- ownerReason = "SHOW_WHEN_LOCKED activity on top"
- )
+ startTransition(KeyguardState.OCCLUDED, "SHOW_WHEN_LOCKED activity on top")
return true
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 76f7749754a2..1b9788f2d401 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -74,8 +74,8 @@ object DeviceEntryIconViewBinder {
val bgView = view.bgView
longPressHandlingView.listener =
object : LongPressHandlingView.Listener {
- override fun onLongPressDetected(view: View, x: Int, y: Int) {
- if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ override fun onLongPressDetected(view: View, x: Int, y: Int, isA11yAction: Boolean) {
+ if (!isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
return
}
vibratorHelper.performHapticFeedback(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index 057b4f9a671d..b387855ad553 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -18,12 +18,15 @@
package com.android.systemui.keyguard.ui.binder
import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.ui.view.LongPressHandlingView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import com.android.systemui.plugins.FalsingManager
object KeyguardLongPressViewBinder {
@@ -43,14 +46,19 @@ object KeyguardLongPressViewBinder {
onSingleTap: () -> Unit,
falsingManager: FalsingManager,
) {
+ view.accessibilityHintLongPressAction =
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+ view.resources.getString(R.string.lock_screen_settings)
+ )
view.listener =
object : LongPressHandlingView.Listener {
- override fun onLongPressDetected(view: View, x: Int, y: Int) {
- if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ override fun onLongPressDetected(view: View, x: Int, y: Int, isA11yAction: Boolean) {
+ if (!isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
return
}
- viewModel.onLongPress()
+ viewModel.onLongPress(isA11yAction)
}
override fun onSingleTapDetected(view: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 940d1e10651a..0532ee285d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -25,6 +25,8 @@ import android.os.Messenger
import android.util.ArrayMap
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.runBlocking
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -55,7 +57,14 @@ constructor(
var observer: PreviewLifecycleObserver? = null
return try {
- val renderer = previewRendererFactory.create(request)
+ val renderer =
+ if (Flags.lockscreenPreviewRendererCreateOnMainThread()) {
+ runBlocking ("$TAG#previewRendererFactory.create", mainDispatcher) {
+ previewRendererFactory.create(request)
+ }
+ } else {
+ previewRendererFactory.create(request)
+ }
observer =
PreviewLifecycleObserver(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index bb4fb7961ed0..e9db1d23ec3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -83,6 +83,7 @@ constructor(
MathUtils.lerp(startAlpha, 0f, it)
}
},
+ onFinish = { 1f },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
index f1cbf256a00f..1d2edc6d406b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
@@ -33,9 +33,12 @@ constructor(
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
- /** Notifies that the user has long-pressed on the lock screen. */
- fun onLongPress() {
- interactor.onLongPress()
+ /** Notifies that the user has long-pressed on the lock screen.
+ *
+ * @param isA11yAction: Whether the action was performed as an a11y action
+ */
+ fun onLongPress(isA11yAction: Boolean) {
+ interactor.onLongPress(isA11yAction)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 46ba5d13bb6d..8811908e1d45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -80,6 +80,7 @@ constructor(
1f - it
}
},
+ onFinish = { 1f },
)
/** Bouncer container alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d1c9b8ea1803..b2ba0e1cd6a6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -169,6 +169,14 @@ public class LogModule {
return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
}
+ /** Provides a logging buffer for all logs related to notification visual stability. */
+ @Provides
+ @SysUISingleton
+ @VisualStabilityLog
+ public static LogBuffer provideVisualStabilityLogBuffer(LogBufferFactory factory) {
+ return factory.create("VisualStabilityLog", 50 /* maxSize */, false /* systrace */);
+ }
+
/** Provides a logging buffer for all logs related to keyguard media controller. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationsRepositoryExt.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt
index 9be7dfe9a1a9..b45ffc1128b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationsRepositoryExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.data.repository
+package com.android.systemui.log.dagger
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
+import javax.inject.Qualifier
-fun FakeHeadsUpNotificationRepository.setNotifications(notifications: List<HeadsUpRowRepository>) {
- setNotifications(*notifications.toTypedArray())
-}
-
-fun FakeHeadsUpNotificationRepository.setNotifications(vararg notifications: HeadsUpRowRepository) {
- this.activeHeadsUpRows.value = notifications.toSet()
-}
+/** A [com.android.systemui.log.LogBuffer] for visual stability-related messages. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class VisualStabilityLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index f004c3a8916f..6c5337418d02 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,44 +187,17 @@ public class MediaProjectionPermissionActivity extends Activity
}
}
- TextPaint paint = new TextPaint();
- paint.setTextSize(42);
-
CharSequence dialogText = null;
CharSequence dialogTitle = null;
- String appName = null;
- if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
+
+ final String appName = extractAppName(aInfo, packageManager);
+ final boolean hasCastingCapabilities =
+ Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
+
+ if (hasCastingCapabilities) {
dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
} else {
- String label = aInfo.loadLabel(packageManager).toString();
-
- // If the label contains new line characters it may push the security
- // message below the fold of the dialog. Labels shouldn't have new line
- // characters anyways, so just truncate the message the first time one
- // is seen.
- final int labelLength = label.length();
- int offset = 0;
- while (offset < labelLength) {
- final int codePoint = label.codePointAt(offset);
- final int type = Character.getType(codePoint);
- if (type == Character.LINE_SEPARATOR
- || type == Character.CONTROL
- || type == Character.PARAGRAPH_SEPARATOR) {
- label = label.substring(0, offset) + ELLIPSIS;
- break;
- }
- offset += Character.charCount(codePoint);
- }
-
- if (label.isEmpty()) {
- label = mPackageName;
- }
-
- String unsanitizedAppName = TextUtils.ellipsize(label,
- paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
- appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
-
String actionText = getString(R.string.media_projection_dialog_warning, appName);
SpannableString message = new SpannableString(actionText);
@@ -255,6 +228,7 @@ public class MediaProjectionPermissionActivity extends Activity
grantMediaProjectionPermission(selectedOption.getMode());
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
+ hasCastingCapabilities,
appName,
overrideDisableSingleAppOption,
mUid,
@@ -289,6 +263,47 @@ public class MediaProjectionPermissionActivity extends Activity
}
}
+ private String extractAppName(ApplicationInfo applicationInfo, PackageManager packageManager) {
+ String label = applicationInfo.loadLabel(packageManager).toString();
+
+ // If the label contains new line characters it may push the security
+ // message below the fold of the dialog. Labels shouldn't have new line
+ // characters anyways, so just truncate the message the first time one
+ // is seen.
+ final int labelLength = label.length();
+ int offset = 0;
+ while (offset < labelLength) {
+ final int codePoint = label.codePointAt(offset);
+ final int type = Character.getType(codePoint);
+ if (type == Character.LINE_SEPARATOR
+ || type == Character.CONTROL
+ || type == Character.PARAGRAPH_SEPARATOR) {
+ label = label.substring(0, offset) + ELLIPSIS;
+ break;
+ }
+ offset += Character.charCount(codePoint);
+ }
+
+ if (label.isEmpty()) {
+ label = mPackageName;
+ }
+
+ TextPaint paint = new TextPaint();
+ paint.setTextSize(42);
+
+ String unsanitizedAppName = TextUtils.ellipsize(label,
+ paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
+ String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
+
+ // Have app name be the package name as a default fallback, if specific app name can't be
+ // extracted
+ if (appName == null || appName.isEmpty()) {
+ return mPackageName;
+ }
+
+ return appName;
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 9ce8070131fa..6d1a4587d089 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -29,13 +29,20 @@ class MediaProjectionPermissionDialogDelegate(
mediaProjectionConfig: MediaProjectionConfig?,
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
- private val appName: String?,
+ private val hasCastingCapabilities: Boolean,
+ appName: String,
forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
+ createOptionList(
+ context,
+ appName,
+ hasCastingCapabilities,
+ mediaProjectionConfig,
+ forceShowPartialScreenshare
+ ),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -43,7 +50,7 @@ class MediaProjectionPermissionDialogDelegate(
override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
super.onCreate(dialog, savedInstanceState)
// TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
- if (appName == null) {
+ if (hasCastingCapabilities) {
setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
} else {
@@ -65,30 +72,29 @@ class MediaProjectionPermissionDialogDelegate(
companion object {
private fun createOptionList(
context: Context,
- appName: String?,
+ appName: String,
+ hasCastingCapabilities: Boolean,
mediaProjectionConfig: MediaProjectionConfig?,
overrideDisableSingleAppOption: Boolean = false,
): List<ScreenShareOption> {
val singleAppWarningText =
- if (appName == null) {
+ if (hasCastingCapabilities) {
R.string.media_projection_entry_cast_permission_dialog_warning_single_app
} else {
R.string.media_projection_entry_app_permission_dialog_warning_single_app
}
val entireScreenWarningText =
- if (appName == null) {
+ if (hasCastingCapabilities) {
R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
} else {
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
- // The single app option should only be disabled if there is an app name provided,
- // the client has setup a MediaProjection with
- // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
- // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
+ // The single app option should only be disabled if the client has setup a
+ // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
+ // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
val singleAppOptionDisabled =
- appName != null &&
- !overrideDisableSingleAppOption &&
+ !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 720120b630d5..5ea8c2183295 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs.tileimpl;
+import static com.android.systemui.Flags.removeUpdateListenerInQsIconViewImpl;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
@@ -204,6 +206,9 @@ public class QSIconViewImpl extends QSIconView {
values.setEvaluator(ArgbEvaluator.getInstance());
mColorAnimator.setValues(values);
mColorAnimator.removeAllListeners();
+ if (removeUpdateListenerInQsIconViewImpl()) {
+ mColorAnimator.removeAllUpdateListeners();
+ }
mColorAnimator.addUpdateListener(animation -> {
setTint(iv, (int) animation.getAnimatedValue());
});
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 6b3dfe1b90ad..dbfe8188b1b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -107,7 +107,9 @@ constructor(
set(value) {
if (field == value) return
field = value
- updateHeight()
+ if (longPressEffect?.state != QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) {
+ updateHeight()
+ }
}
override var squishinessFraction: Float = 1f
@@ -381,14 +383,6 @@ constructor(
}
private fun updateHeight() {
- // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the
- // launch animation.
- if (!haveLongPressPropertiesBeenReset && longPressEffect != null) {
- // The launch animation of a long-press effect did not reset the long-press effect so
- // we must do it here
- resetLongPressEffectProperties()
- longPressEffect.resetState()
- }
val actualHeight =
if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
heightOverride
@@ -417,17 +411,17 @@ constructor(
}
override fun init(tile: QSTile) {
- val expandable = Expandable.fromView(this)
if (longPressEffect != null) {
isHapticFeedbackEnabled = false
longPressEffect.qsTile = tile
- longPressEffect.expandable = expandable
+ longPressEffect.createExpandableFromView(this)
initLongPressEffectCallback()
init(
{ _: View -> longPressEffect.onTileClick() },
null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
)
} else {
+ val expandable = Expandable.fromView(this)
init(
{ _: View? -> tile.click(expandable) },
{ _: View? ->
@@ -475,10 +469,10 @@ constructor(
}
}
- override fun onReverseAnimator() {
+ override fun onReverseAnimator(playHaptics: Boolean) {
longPressEffectAnimator?.let {
val pausedProgress = it.animatedFraction
- longPressEffect?.playReverseHaptics(pausedProgress)
+ if (playHaptics) longPressEffect?.playReverseHaptics(pausedProgress)
it.reverse()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 51447cc6f373..72f37fc98e17 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -187,7 +187,6 @@ constructor(
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
deviceProvisioningInteractor.isDeviceProvisioned
- .distinctUntilChanged()
.flatMapLatest { isAllowedToBeVisible ->
if (isAllowedToBeVisible) {
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 49144091cb62..54ae225442c9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -49,6 +49,7 @@ import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
/**
* An AsyncTask that saves an image to the media store in the background.
@@ -59,12 +60,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
+ /**
+ * POD used in the AsyncTask which saves an image in the background.
+ */
+ static class SaveImageInBackgroundData {
+ public Bitmap image;
+ public Consumer<Uri> finisher;
+ public ActionsReadyListener mActionsReadyListener;
+ public QuickShareActionReadyListener mQuickShareActionsReadyListener;
+ public UserHandle owner;
+ public int displayId;
+
+ void clearImage() {
+ image = null;
+ }
+ }
+
+ /**
+ * Structure returned by the SaveImageInBackgroundTask
+ */
+ public static class SavedImageData {
+ public Uri uri;
+ public List<Notification.Action> smartActions;
+ public Notification.Action quickShareAction;
+ public UserHandle owner;
+ public String subject; // Title for sharing
+ public Long imageTime; // Time at which screenshot was saved
+
+ /**
+ * Used to reset the return data on error
+ */
+ public void reset() {
+ uri = null;
+ smartActions = null;
+ quickShareAction = null;
+ subject = null;
+ imageTime = null;
+ }
+ }
+
+ /**
+ * Structure returned by the QueryQuickShareInBackgroundTask
+ */
+ static class QuickShareData {
+ public Notification.Action quickShareAction;
+
+ /**
+ * Used to reset the return data on error
+ */
+ public void reset() {
+ quickShareAction = null;
+ }
+ }
+
+ interface ActionsReadyListener {
+ void onActionsReady(SavedImageData imageData);
+ }
+
+ interface QuickShareActionReadyListener {
+ void onActionsReady(QuickShareData quickShareData);
+ }
+
private final Context mContext;
private FeatureFlags mFlags;
private final ScreenshotSmartActions mScreenshotSmartActions;
- private final ScreenshotController.SaveImageInBackgroundData mParams;
- private final ScreenshotController.SavedImageData mImageData;
- private final ScreenshotController.QuickShareData mQuickShareData;
+ private final SaveImageInBackgroundData mParams;
+ private final SavedImageData mImageData;
+ private final QuickShareData mQuickShareData;
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private String mScreenshotId;
@@ -77,15 +139,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
FeatureFlags flags,
ImageExporter exporter,
ScreenshotSmartActions screenshotSmartActions,
- ScreenshotController.SaveImageInBackgroundData data,
+ SaveImageInBackgroundData data,
ScreenshotNotificationSmartActionsProvider
screenshotNotificationSmartActionsProvider
) {
mContext = context;
mFlags = flags;
mScreenshotSmartActions = screenshotSmartActions;
- mImageData = new ScreenshotController.SavedImageData();
- mQuickShareData = new ScreenshotController.QuickShareData();
+ mImageData = new SavedImageData();
+ mQuickShareData = new QuickShareData();
mImageExporter = exporter;
// Prepare all the output metadata
@@ -195,7 +257,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
* Update the listener run when the saving task completes. Used to avoid showing UI for the
* first screenshot when a second one is taken.
*/
- void setActionsReadyListener(ScreenshotController.ActionsReadyListener listener) {
+ void setActionsReadyListener(ActionsReadyListener listener) {
mParams.mActionsReadyListener = listener;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 773900981759..0a4635e0849b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -35,10 +35,7 @@ import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.ExitTransitionCoordinator;
import android.app.ICompatCameraControlCallback;
-import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -55,7 +52,6 @@ import android.os.UserManager;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.Pair;
import android.view.Display;
import android.view.ScrollCaptureResponse;
import android.view.View;
@@ -67,7 +63,6 @@ import android.view.WindowManager;
import android.widget.Toast;
import android.window.WindowContext;
-import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
@@ -89,7 +84,6 @@ import dagger.assisted.AssistedInject;
import kotlin.Unit;
-import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -104,67 +98,6 @@ import javax.inject.Provider;
public class ScreenshotController implements ScreenshotHandler {
private static final String TAG = logTag(ScreenshotController.class);
- /**
- * POD used in the AsyncTask which saves an image in the background.
- */
- static class SaveImageInBackgroundData {
- public Bitmap image;
- public Consumer<Uri> finisher;
- public ScreenshotController.ActionsReadyListener mActionsReadyListener;
- public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
- public UserHandle owner;
- public int displayId;
-
- void clearImage() {
- image = null;
- }
- }
-
- /**
- * Structure returned by the SaveImageInBackgroundTask
- */
- public static class SavedImageData {
- public Uri uri;
- public List<Notification.Action> smartActions;
- public Notification.Action quickShareAction;
- public UserHandle owner;
- public String subject; // Title for sharing
- public Long imageTime; // Time at which screenshot was saved
-
- /**
- * Used to reset the return data on error
- */
- public void reset() {
- uri = null;
- smartActions = null;
- quickShareAction = null;
- subject = null;
- imageTime = null;
- }
- }
-
- /**
- * Structure returned by the QueryQuickShareInBackgroundTask
- */
- static class QuickShareData {
- public Notification.Action quickShareAction;
-
- /**
- * Used to reset the return data on error
- */
- public void reset() {
- quickShareAction = null;
- }
- }
-
- interface ActionsReadyListener {
- void onActionsReady(ScreenshotController.SavedImageData imageData);
- }
-
- interface QuickShareActionReadyListener {
- void onActionsReady(ScreenshotController.QuickShareData quickShareData);
- }
-
public interface TransitionDestination {
/**
* Allows the long screenshot activity to call back with a destination location (the bounds
@@ -213,7 +146,6 @@ public class ScreenshotController implements ScreenshotHandler {
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
- private final ActionIntentExecutor mActionIntentExecutor;
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
private final ActionExecutor mActionExecutor;
@@ -259,7 +191,6 @@ public class ScreenshotController implements ScreenshotHandler {
BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
ScreenshotActionsController.Factory screenshotActionsControllerFactory,
- ActionIntentExecutor actionIntentExecutor,
ActionExecutor.Factory actionExecutorFactory,
UserManager userManager,
AssistContentRequester assistContentRequester,
@@ -289,7 +220,6 @@ public class ScreenshotController implements ScreenshotHandler {
final Context displayContext = context.createDisplayContext(display);
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mFlags = flags;
- mActionIntentExecutor = actionIntentExecutor;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
@@ -765,33 +695,6 @@ public class ScreenshotController implements ScreenshotHandler {
mScreenshotAnimation.start();
}
- /**
- * Supplies the necessary bits for the shared element transition to share sheet.
- * Note that once called, the action intent to share must be sent immediately after.
- */
- private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() {
- ExitTransitionCoordinator.ExitTransitionCallbacks callbacks =
- new ExitTransitionCoordinator.ExitTransitionCallbacks() {
- @Override
- public boolean isReturnTransitionAllowed() {
- return false;
- }
-
- @Override
- public void hideSharedElements() {
- finishDismiss();
- }
-
- @Override
- public void onFinish() {
- }
- };
-
- return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
- Pair.create(mViewProxy.getScreenshotPreview(),
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
- }
-
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
@@ -838,11 +741,11 @@ public class ScreenshotController implements ScreenshotHandler {
private void saveScreenshotInWorkerThread(
UserHandle owner,
@NonNull Consumer<Uri> finisher,
- @Nullable ActionsReadyListener actionsReadyListener,
- @Nullable QuickShareActionReadyListener
+ @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener,
+ @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener
quickShareActionsReadyListener) {
- ScreenshotController.SaveImageInBackgroundData
- data = new ScreenshotController.SaveImageInBackgroundData();
+ SaveImageInBackgroundTask.SaveImageInBackgroundData
+ data = new SaveImageInBackgroundTask.SaveImageInBackgroundData();
data.image = mScreenBitmap;
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
@@ -881,7 +784,7 @@ public class ScreenshotController implements ScreenshotHandler {
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
- private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
+ private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) {
logScreenshotResultStatus(imageData.uri, imageData.owner);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index 4f27b9e4dbf0..b3d5c9e9691c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -99,7 +99,7 @@ class PolicyRequestProcessor(
original,
updates.component,
updates.owner,
- type.displayId
+ type.displayId,
)
}
return updated
@@ -120,6 +120,7 @@ class PolicyRequestProcessor(
return replaceWithScreenshot(
original = original,
componentName = topMainRootTask?.topActivity ?: defaultComponent,
+ taskId = topMainRootTask?.taskId,
owner = defaultOwner,
displayId = original.displayId
)
@@ -144,11 +145,12 @@ class PolicyRequestProcessor(
)
}
- suspend fun replaceWithScreenshot(
+ private suspend fun replaceWithScreenshot(
original: ScreenshotData,
componentName: ComponentName?,
owner: UserHandle?,
displayId: Int,
+ taskId: Int? = null,
): ScreenshotData {
Log.i(TAG, "Capturing screenshot: $componentName / $owner")
val screenshot = captureDisplay(displayId)
@@ -157,7 +159,8 @@ class PolicyRequestProcessor(
bitmap = screenshot,
userHandle = owner,
topComponent = componentName,
- screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0)
+ screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0),
+ taskId = taskId ?: -1,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index d870fe69463d..b468d0e75a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -50,19 +50,21 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.collectFlow
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/**
@@ -77,6 +79,7 @@ constructor(
private val communalInteractor: CommunalInteractor,
private val communalViewModel: CommunalViewModel,
private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
private val communalColors: CommunalColors,
@@ -149,6 +152,19 @@ constructor(
private var hubShowing = false
/**
+ * True if we're transitioning to or from edit mode
+ *
+ * We block all touches and gestures when edit mode is open to prevent funky transition issues
+ * when entering and exiting edit mode because we delay exiting the hub scene when entering edit
+ * mode and enter the hub scene early when exiting edit mode to make for a smoother transition.
+ * Gestures during these transitions can result in broken and unexpected UI states.
+ *
+ * Tracks [CommunalInteractor.editActivityShowing] and the [KeyguardState.GONE] to
+ * [KeyguardState.GLANCEABLE_HUB] transition.
+ */
+ private var inEditModeTransition = false
+
+ /**
* True if either the primary or alternate bouncer are open, meaning the hub should not receive
* any touch input.
*/
@@ -165,7 +181,14 @@ constructor(
*
* Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting].
*/
- private var shadeShowing = false
+ private var shadeShowingAndConsumingTouches = false
+
+ /**
+ * True if the shade ever fully expands and the user isn't interacting with it (aka finger on
+ * screen dragging). In this case, the shade should handle all touch events until it has fully
+ * collapsed.
+ */
+ private var userNotInteractiveAtShadeFullyExpanded = false
/**
* True if the device is dreaming, in which case we shouldn't do anything for top/bottom swipes
@@ -317,9 +340,41 @@ constructor(
)
collectFlow(
containerView,
- allOf(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
+ // When leaving edit mode, editActivityShowing is true until the edit mode activity
+ // finishes itself and the device locks, after which isInTransition will be true until
+ // we're fully on the hub.
+ anyOf(
+ communalInteractor.editActivityShowing,
+ keyguardTransitionInteractor.isInTransition(
+ Edge.create(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
+ )
+ ),
{
- shadeShowing = it
+ inEditModeTransition = it
+ updateTouchHandlingState()
+ }
+ )
+ collectFlow(
+ containerView,
+ combine(
+ shadeInteractor.isAnyFullyExpanded,
+ shadeInteractor.isUserInteracting,
+ shadeInteractor.isShadeFullyCollapsed,
+ ::Triple
+ ),
+ { (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) ->
+ val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting
+
+ // If we ever are fully expanded and not interacting, capture this state as we
+ // should not handle touches until we fully collapse again
+ userNotInteractiveAtShadeFullyExpanded =
+ !isShadeFullyCollapsed &&
+ (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive)
+
+ // If the shade reaches full expansion without interaction, then we should allow it
+ // to consume touches rather than handling it here until it disappears.
+ shadeShowingAndConsumingTouches =
+ userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive
updateTouchHandlingState()
}
)
@@ -337,7 +392,11 @@ constructor(
* Also clears gesture exclusion zones when the hub is occluded or gone.
*/
private fun updateTouchHandlingState() {
- val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
+ // Only listen to gestures when we're settled in the hub keyguard state and the shade
+ // bouncer are not showing on top.
+ val shouldInterceptGestures =
+ hubShowing &&
+ !(shadeShowingAndConsumingTouches || anyBouncerShowing || inEditModeTransition)
if (shouldInterceptGestures) {
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
} else {
@@ -389,17 +448,18 @@ constructor(
return false
}
- return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+ return communalContainerView?.let { handleTouchEventOnCommunalView(ev) } ?: false
}
- private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
+ private fun handleTouchEventOnCommunalView(ev: MotionEvent): Boolean {
val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
val isUp = ev.actionMasked == MotionEvent.ACTION_UP
+ val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE
val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
- val hubOccluded = anyBouncerShowing || shadeShowing
+ val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
- if (isDown && !hubOccluded) {
+ if ((isDown || isMove) && !hubOccluded) {
isTrackingHubTouch = true
}
@@ -407,7 +467,7 @@ constructor(
if (isUp || isCancel) {
isTrackingHubTouch = false
}
- return dispatchTouchEvent(view, ev)
+ return dispatchTouchEvent(ev)
}
return false
@@ -417,7 +477,14 @@ constructor(
* Dispatches the touch event to the communal container and sends a user activity event to reset
* the screen timeout.
*/
- private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean {
+ private fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ if (inEditModeTransition) {
+ // Consume but ignore touches while we're transitioning to or from edit mode so that the
+ // user can't trigger another transition, such as by swiping the hub away, tapping a
+ // widget, or opening the shade/bouncer. Doing any of these while transitioning can
+ // result in broken states.
+ return true
+ }
try {
var handled = false
communalContainerWrapper?.dispatchTouchEvent(ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 5065baa04623..23e2620ac6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -76,7 +76,7 @@ constructor(
scope.launch {
shadeInteractor.isAnyExpanded.collect {
if (!it) {
- runPostCollapseActions()
+ withContext(mainDispatcher) { runPostCollapseActions() }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c9126161c40f..b9d24abc4037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -51,6 +51,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Pair;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -200,6 +201,7 @@ public class CommandQueue extends IStatusBar.Stub implements
* event.
*/
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
+ private final Context mContext;
private final DisplayTracker mDisplayTracker;
private final @Nullable CommandRegistry mRegistry;
private final @Nullable DumpHandler mDumpHandler;
@@ -571,6 +573,7 @@ public class CommandQueue extends IStatusBar.Stub implements
DumpHandler dumpHandler,
Lazy<PowerInteractor> powerInteractor
) {
+ mContext = context;
mDisplayTracker = displayTracker;
mRegistry = registry;
mDumpHandler = dumpHandler;
@@ -1209,7 +1212,12 @@ public class CommandQueue extends IStatusBar.Stub implements
boolean showImeSwitcher) {
if (displayId == INVALID_DISPLAY) return;
- if (mLastUpdatedImeDisplayId != displayId
+ boolean isConcurrentMultiUserModeEnabled = UserManager.isVisibleBackgroundUsersEnabled()
+ && mContext.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled)
+ && android.view.inputmethod.Flags.concurrentInputMethods();
+
+ if (!isConcurrentMultiUserModeEnabled
+ && mLastUpdatedImeDisplayId != displayId
&& mLastUpdatedImeDisplayId != INVALID_DISPLAY) {
// Set previous NavBar's IME window status as invisible when IME
// window switched to another display for single-session IME case.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index b2140f793ced..130b1170c9f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.chips.ui.model
import android.content.Context
import android.content.res.ColorStateList
+import android.view.ContextThemeWrapper
import androidx.annotation.ColorInt
import com.android.settingslib.Utils
import com.android.systemui.res.R
@@ -41,8 +42,11 @@ sealed interface ColorsModel {
/** The chip should have a red background with white text. */
data object Red : ColorsModel {
- override fun background(context: Context): ColorStateList =
- ColorStateList.valueOf(context.getColor(R.color.GM2_red_600))
+ override fun background(context: Context): ColorStateList {
+ val themedContext =
+ ContextThemeWrapper(context, com.android.internal.R.style.Theme_DeviceDefault_Light)
+ return Utils.getColorAttr(themedContext, com.android.internal.R.attr.materialColorError)
+ }
override fun text(context: Context) = context.getColor(android.R.color.white)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index 3dcaff3de35a..b342722ebb09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -52,8 +52,11 @@ constructor(val proxy: DeviceConfigProxy, val context: Context) {
}
fun getNotificationBuckets(): IntArray {
- if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled
- || NotificationClassificationFlag.isEnabled) {
+ if (
+ PriorityPeopleSection.isEnabled ||
+ NotificationMinimalismPrototype.isEnabled ||
+ NotificationClassificationFlag.isEnabled
+ ) {
// We don't need this list to be adaptive, it can be the superset of all features.
return PriorityBucket.getAllInOrder()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index e48c28d3f3ee..cb133ecadab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -1005,6 +1005,16 @@ public final class NotificationEntry extends ListEntry implements NotificationRo
mIsMarkedForUserTriggeredMovement = marked;
}
+ private boolean mSeenInShade = false;
+
+ public void setSeenInShade(boolean seen) {
+ mSeenInShade = seen;
+ }
+
+ public boolean isSeenInShade() {
+ return mSeenInShade;
+ }
+
public void setIsHeadsUpEntry(boolean isHeadsUpEntry) {
mIsHeadsUpEntry = isHeadsUpEntry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
new file mode 100644
index 000000000000..a6605f652ff3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.collection.coordinator
+
+import android.annotation.SuppressLint
+import android.app.NotificationManager
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
+import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.printCollection
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+
+/**
+ * If the setting is enabled, this will track seen notifications and ensure that they only show in
+ * the shelf on the lockscreen.
+ *
+ * This class is a replacement of the [OriginalUnseenKeyguardCoordinator].
+ */
+@CoordinatorScope
+@SuppressLint("SharedFlowCreation")
+class LockScreenMinimalismCoordinator
+@Inject
+constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val dumpManager: DumpManager,
+ private val headsUpInteractor: HeadsUpNotificationInteractor,
+ private val logger: LockScreenMinimalismCoordinatorLogger,
+ @Application private val scope: CoroutineScope,
+ private val secureSettings: SecureSettings,
+ private val seenNotificationsInteractor: SeenNotificationsInteractor,
+ private val statusBarStateController: StatusBarStateController,
+ private val shadeInteractor: ShadeInteractor,
+) : Coordinator, Dumpable {
+
+ private val unseenNotifications = mutableSetOf<NotificationEntry>()
+ private var isShadeVisible = false
+ private var unseenFilterEnabled = false
+
+ override fun attach(pipeline: NotifPipeline) {
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+ pipeline.addPromoter(unseenNotifPromoter)
+ pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
+ pipeline.addCollectionListener(collectionListener)
+ scope.launch { trackUnseenFilterSettingChanges() }
+ dumpManager.registerDumpable(this)
+ }
+
+ private suspend fun trackSeenNotifications() {
+ coroutineScope {
+ launch { clearUnseenNotificationsWhenShadeIsExpanded() }
+ launch { markHeadsUpNotificationsAsSeen() }
+ }
+ }
+
+ private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
+ shadeInteractor.isShadeFullyExpanded.collectLatest { isExpanded ->
+ // Give keyguard events time to propagate, in case this expansion is part of the
+ // keyguard transition and not the user expanding the shade
+ delay(SHADE_VISIBLE_SEEN_TIMEOUT)
+ isShadeVisible = isExpanded
+ if (isExpanded) {
+ logger.logShadeVisible(unseenNotifications.size)
+ unseenNotifications.clear()
+ // no need to invalidateList; filtering is inactive while shade is open
+ } else {
+ logger.logShadeHidden()
+ }
+ }
+ }
+
+ private suspend fun markHeadsUpNotificationsAsSeen() {
+ headsUpInteractor.topHeadsUpRowIfPinned
+ .map { it?.let { headsUpInteractor.notificationKey(it) } }
+ .collectLatest { key ->
+ if (key == null) {
+ logger.logTopHeadsUpRow(key = null, wasUnseenWhenPinned = false)
+ } else {
+ val wasUnseenWhenPinned = unseenNotifications.any { it.key == key }
+ logger.logTopHeadsUpRow(key, wasUnseenWhenPinned)
+ if (wasUnseenWhenPinned) {
+ delay(HEADS_UP_SEEN_TIMEOUT)
+ val wasUnseenAfterDelay = unseenNotifications.removeIf { it.key == key }
+ logger.logHunHasBeenSeen(key, wasUnseenAfterDelay)
+ // no need to invalidateList; nothing should change until after heads up
+ }
+ }
+ }
+ }
+
+ private fun unseenFeatureEnabled(): Flow<Boolean> {
+ // TODO(b/330387368): create LOCK_SCREEN_NOTIFICATION_MINIMALISM setting to use here?
+ // Or should we actually just repurpose using the existing setting?
+ if (NotificationMinimalismPrototype.isEnabled) {
+ return flowOf(true)
+ }
+ return secureSettings
+ // emit whenever the setting has changed
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getIntForUser(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
+ ) == 1
+ }
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
+ }
+
+ private suspend fun trackUnseenFilterSettingChanges() {
+ unseenFeatureEnabled().collectLatest { isSettingEnabled ->
+ // update local field and invalidate if necessary
+ if (isSettingEnabled != unseenFilterEnabled) {
+ unseenFilterEnabled = isSettingEnabled
+ unseenNotifPromoter.invalidateList("unseen setting changed")
+ }
+ // if the setting is enabled, then start tracking and filtering unseen notifications
+ logger.logTrackingUnseen(isSettingEnabled)
+ if (isSettingEnabled) {
+ trackSeenNotifications()
+ }
+ }
+ }
+
+ private val collectionListener =
+ object : NotifCollectionListener {
+ override fun onEntryAdded(entry: NotificationEntry) {
+ if (!isShadeVisible) {
+ logger.logUnseenAdded(entry.key)
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ if (!isShadeVisible) {
+ logger.logUnseenUpdated(entry.key)
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ if (unseenNotifications.remove(entry)) {
+ logger.logUnseenRemoved(entry.key)
+ }
+ }
+ }
+
+ private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ // Only ever elevate a top unseen notification on keyguard, not even locked shade
+ if (statusBarStateController.state != StatusBarState.KEYGUARD) {
+ seenNotificationsInteractor.setTopOngoingNotification(null)
+ seenNotificationsInteractor.setTopUnseenNotification(null)
+ return
+ }
+ // On keyguard pick the top-ranked unseen or ongoing notification to elevate
+ val nonSummaryEntries: Sequence<NotificationEntry> =
+ list
+ .asSequence()
+ .flatMap {
+ when (it) {
+ is NotificationEntry -> listOfNotNull(it)
+ is GroupEntry -> it.children
+ else -> error("unhandled type of $it")
+ }
+ }
+ .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
+ seenNotificationsInteractor.setTopOngoingNotification(
+ nonSummaryEntries
+ .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
+ .minByOrNull { it.ranking.rank }
+ )
+ seenNotificationsInteractor.setTopUnseenNotification(
+ nonSummaryEntries
+ .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
+ .minByOrNull { it.ranking.rank }
+ )
+ }
+
+ @VisibleForTesting
+ val unseenNotifPromoter =
+ object : NotifPromoter(TAG) {
+ override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
+ when {
+ NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode() -> false
+ seenNotificationsInteractor.isTopOngoingNotification(child) -> true
+ !NotificationMinimalismPrototype.ungroupTopUnseen -> false
+ else -> seenNotificationsInteractor.isTopUnseenNotification(child)
+ }
+ }
+
+ val topOngoingSectioner =
+ object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
+ override fun isInSection(entry: ListEntry): Boolean {
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return false
+ return entry.anyEntry { notificationEntry ->
+ seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
+ }
+ }
+ }
+
+ val topUnseenSectioner =
+ object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
+ override fun isInSection(entry: ListEntry): Boolean {
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return false
+ return entry.anyEntry { notificationEntry ->
+ seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
+ }
+ }
+ }
+
+ private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
+ when {
+ predicate(representativeEntry) -> true
+ this !is GroupEntry -> false
+ else -> children.any(predicate)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) =
+ with(pw.asIndenting()) {
+ seenNotificationsInteractor.dump(this)
+ printCollection("unseen notifications", unseenNotifications) { println(it.key) }
+ }
+
+ companion object {
+ private const val TAG = "LockScreenMinimalismCoordinator"
+ private val SHADE_VISIBLE_SEEN_TIMEOUT = 0.25.seconds
+ private val HEADS_UP_SEEN_TIMEOUT = 0.75.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
new file mode 100644
index 000000000000..e44a77c30999
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.UnseenNotificationLog
+import javax.inject.Inject
+
+private const val TAG = "LockScreenMinimalismCoordinator"
+
+class LockScreenMinimalismCoordinatorLogger
+@Inject
+constructor(
+ @UnseenNotificationLog private val buffer: LogBuffer,
+) {
+
+ fun logTrackingUnseen(trackingUnseen: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { bool1 = trackingUnseen },
+ messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
+ )
+
+ fun logShadeVisible(numUnseen: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { int1 = numUnseen },
+ messagePrinter = { "Shade expanded. Notifications marked as seen: $int1" }
+ )
+ }
+
+ fun logShadeHidden() {
+ buffer.log(TAG, LogLevel.DEBUG, "Shade no longer expanded.")
+ }
+
+ fun logUnseenAdded(key: String) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = key },
+ messagePrinter = { "Unseen notif added: $str1" },
+ )
+
+ fun logUnseenUpdated(key: String) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = key },
+ messagePrinter = { "Unseen notif updated: $str1" },
+ )
+
+ fun logUnseenRemoved(key: String) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = key },
+ messagePrinter = { "Unseen notif removed: $str1" },
+ )
+
+ fun logHunHasBeenSeen(key: String, wasUnseen: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = {
+ str1 = key
+ bool1 = wasUnseen
+ },
+ messagePrinter = { "Heads up notif has been seen: $str1 wasUnseen=$bool1" },
+ )
+
+ fun logTopHeadsUpRow(key: String?, wasUnseenWhenPinned: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = {
+ str1 = key
+ bool1 = wasUnseenWhenPinned
+ },
+ messagePrinter = { "New notif is top heads up: $str1 wasUnseen=$bool1" },
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 99327d1fe116..73ce48b2324a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -47,6 +47,7 @@ constructor(
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
keyguardCoordinator: KeyguardCoordinator,
unseenKeyguardCoordinator: OriginalUnseenKeyguardCoordinator,
+ lockScreenMinimalismCoordinator: LockScreenMinimalismCoordinator,
rankingCoordinator: RankingCoordinator,
colorizedFgsCoordinator: ColorizedFgsCoordinator,
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
@@ -87,7 +88,11 @@ constructor(
mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
mCoordinators.add(hideNotifsForOtherUsersCoordinator)
mCoordinators.add(keyguardCoordinator)
- mCoordinators.add(unseenKeyguardCoordinator)
+ if (NotificationMinimalismPrototype.isEnabled) {
+ mCoordinators.add(lockScreenMinimalismCoordinator)
+ } else {
+ mCoordinators.add(unseenKeyguardCoordinator)
+ }
mCoordinators.add(rankingCoordinator)
mCoordinators.add(colorizedFgsCoordinator)
mCoordinators.add(deviceProvisionedCoordinator)
@@ -120,12 +125,12 @@ constructor(
}
// Manually add Ordered Sections
- if (NotificationMinimalismPrototype.V2.isEnabled) {
- mOrderedSections.add(unseenKeyguardCoordinator.topOngoingSectioner) // Top Ongoing
+ if (NotificationMinimalismPrototype.isEnabled) {
+ mOrderedSections.add(lockScreenMinimalismCoordinator.topOngoingSectioner) // Top Ongoing
}
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
- if (NotificationMinimalismPrototype.V2.isEnabled) {
- mOrderedSections.add(unseenKeyguardCoordinator.topUnseenSectioner) // Top Unseen
+ if (NotificationMinimalismPrototype.isEnabled) {
+ mOrderedSections.add(lockScreenMinimalismCoordinator.topUnseenSectioner) // Top Unseen
}
mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
if (PriorityPeopleSection.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 5dd1663f712f..5b25b117c761 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
-import android.app.NotificationManager
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
@@ -30,21 +29,14 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
-import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
-import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -73,9 +65,12 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
- * lockscreen.
+ * If the setting is enabled, this will track and hide seen notifications on the lockscreen.
+ *
+ * This is the "original" unseen keyguard coordinator because this is the logic originally developed
+ * for large screen devices where showing "seen" notifications on the lock screen was distracting.
+ * Moreover, this file was created during a project that will replace this logic, so the
+ * [LockScreenMinimalismCoordinator] is the expected replacement of this file.
*/
@CoordinatorScope
@SuppressLint("SharedFlowCreation")
@@ -100,10 +95,7 @@ constructor(
private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
- if (NotificationMinimalismPrototype.V2.isEnabled) {
- pipeline.addPromoter(unseenNotifPromoter)
- pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
- }
+ NotificationMinimalismPrototype.assertInLegacyMode()
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
scope.launch { trackUnseenFilterSettingChanges() }
@@ -112,6 +104,7 @@ constructor(
private suspend fun trackSeenNotifications() {
// Whether or not keyguard is visible (or occluded).
+ @Suppress("DEPRECATION")
val isKeyguardPresentFlow: Flow<Boolean> =
keyguardTransitionInteractor
.transitionValue(
@@ -265,11 +258,9 @@ constructor(
}
private fun unseenFeatureEnabled(): Flow<Boolean> {
- if (
- NotificationMinimalismPrototype.V1.isEnabled ||
- NotificationMinimalismPrototype.V2.isEnabled
- ) {
- return flowOf(true)
+ if (NotificationMinimalismPrototype.isEnabled) {
+ // TODO(b/330387368): should this really just be turned off? If so, hide the setting.
+ return flowOf(false)
}
return secureSettings
// emit whenever the setting has changed
@@ -340,110 +331,18 @@ constructor(
}
}
- private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
- // Only ever elevate a top unseen notification on keyguard, not even locked shade
- if (statusBarStateController.state != StatusBarState.KEYGUARD) {
- seenNotificationsInteractor.setTopOngoingNotification(null)
- seenNotificationsInteractor.setTopUnseenNotification(null)
- return
- }
- // On keyguard pick the top-ranked unseen or ongoing notification to elevate
- val nonSummaryEntries: Sequence<NotificationEntry> =
- list
- .asSequence()
- .flatMap {
- when (it) {
- is NotificationEntry -> listOfNotNull(it)
- is GroupEntry -> it.children
- else -> error("unhandled type of $it")
- }
- }
- .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
- seenNotificationsInteractor.setTopOngoingNotification(
- nonSummaryEntries
- .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
- .minByOrNull { it.ranking.rank }
- )
- seenNotificationsInteractor.setTopUnseenNotification(
- nonSummaryEntries
- .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
- .minByOrNull { it.ranking.rank }
- )
- }
-
- @VisibleForTesting
- val unseenNotifPromoter =
- object : NotifPromoter("$TAG-unseen") {
- override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
- else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false
- else
- seenNotificationsInteractor.isTopOngoingNotification(child) ||
- seenNotificationsInteractor.isTopUnseenNotification(child)
- }
-
- val topOngoingSectioner =
- object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
- override fun isInSection(entry: ListEntry): Boolean {
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
- return entry.anyEntry { notificationEntry ->
- seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
- }
- }
- }
-
- val topUnseenSectioner =
- object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
- override fun isInSection(entry: ListEntry): Boolean {
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
- return entry.anyEntry { notificationEntry ->
- seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
- }
- }
- }
-
- private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
- when {
- predicate(representativeEntry) -> true
- this !is GroupEntry -> false
- else -> children.any(predicate)
- }
-
@VisibleForTesting
val unseenNotifFilter =
- object : NotifFilter("$TAG-unseen") {
+ object : NotifFilter(TAG) {
var hasFilteredAnyNotifs = false
- /**
- * Encapsulates a definition of "being on the keyguard". Note that these two definitions
- * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
- * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
- * is any state where the keyguard has not been dismissed, including locked shade and
- * occluded lock screen.
- *
- * Returning false for locked shade and occluded states means that this filter will
- * allow seen notifications to appear in the locked shade.
- */
- private fun isOnKeyguard(): Boolean =
- if (NotificationMinimalismPrototype.V2.isEnabled) {
- false // disable this feature under this prototype
- } else if (
- NotificationMinimalismPrototype.V1.isEnabled &&
- NotificationMinimalismPrototype.V1.showOnLockedShade
- ) {
- statusBarStateController.state == StatusBarState.KEYGUARD
- } else {
- keyguardRepository.isKeyguardShowing()
- }
-
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
when {
// Don't apply filter if the setting is disabled
!unseenFilterEnabled -> false
// Don't apply filter if the keyguard isn't currently showing
- !isOnKeyguard() -> false
+ !keyguardRepository.isKeyguardShowing() -> false
// Don't apply the filter if the notification is unseen
unseenNotifications.contains(entry) -> false
// Don't apply the filter to (non-promoted) group summaries
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index caa6c17ac3d2..696298e82db9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -18,8 +18,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
-import android.util.Log;
-
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -29,7 +27,10 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
@@ -41,7 +42,6 @@ import com.android.systemui.statusbar.notification.collection.provider.VisualSta
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.Compile;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -61,8 +61,6 @@ import javax.inject.Inject;
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable {
- public static final String TAG = "VisualStability";
- public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private final DelayableExecutor mDelayableExecutor;
private final HeadsUpManager mHeadsUpManager;
private final SeenNotificationsInteractor mSeenNotificationsInteractor;
@@ -73,6 +71,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final CommunalInteractor mCommunalInteractor;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final VisualStabilityCoordinatorLogger mLogger;
private boolean mSleepy = true;
private boolean mFullyDozed;
@@ -81,6 +81,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private boolean mNotifPanelCollapsing;
private boolean mNotifPanelLaunchingActivity;
private boolean mCommunalShowing = false;
+ private boolean mLockscreenShowing = false;
private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
@@ -109,7 +110,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
VisibilityLocationProvider visibilityLocationProvider,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle,
- CommunalInteractor communalInteractor) {
+ CommunalInteractor communalInteractor,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ VisualStabilityCoordinatorLogger logger) {
mHeadsUpManager = headsUpManager;
mShadeAnimationInteractor = shadeAnimationInteractor;
mJavaAdapter = javaAdapter;
@@ -120,6 +123,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
mCommunalInteractor = communalInteractor;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mLogger = logger;
dumpManager.registerDumpable(this);
}
@@ -138,6 +143,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
this::onLaunchingActivityChanged);
mJavaAdapter.alwaysCollectFlow(mCommunalInteractor.isIdleOnCommunal(),
this::onCommunalShowingChanged);
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
+ KeyguardState.LOCKSCREEN),
+ this::onLockscreenKeyguardStateTransitionValueChanged);
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -150,8 +158,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
if (entry == null) {
return false;
}
- boolean isTopUnseen = NotificationMinimalismPrototype.V2.isEnabled()
- && mSeenNotificationsInteractor.isTopUnseenNotification(entry);
+ boolean isTopUnseen = NotificationMinimalismPrototype.isEnabled()
+ && (mSeenNotificationsInteractor.isTopUnseenNotification(entry)
+ || mSeenNotificationsInteractor.isTopOngoingNotification(entry));
if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
return !mVisibilityLocationProvider.isInVisibleLocation(entry);
}
@@ -220,12 +229,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
boolean wasReorderingAllowed = mReorderingAllowed;
mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
mReorderingAllowed = isReorderingAllowed();
- if (DEBUG && (wasPipelineRunAllowed != mPipelineRunAllowed
- || wasReorderingAllowed != mReorderingAllowed)) {
- Log.d(TAG, "Stability allowances changed:"
- + " pipelineRunAllowed " + wasPipelineRunAllowed + "->" + mPipelineRunAllowed
- + " reorderingAllowed " + wasReorderingAllowed + "->" + mReorderingAllowed
- + " when setting " + field + "=" + value);
+ if (wasPipelineRunAllowed != mPipelineRunAllowed
+ || wasReorderingAllowed != mReorderingAllowed) {
+ mLogger.logAllowancesChanged(
+ wasPipelineRunAllowed, mPipelineRunAllowed,
+ wasReorderingAllowed, mReorderingAllowed,
+ field, value);
}
if (mPipelineRunAllowed && mIsSuppressingPipelineRun) {
mNotifStabilityManager.invalidateList("pipeline run suppression ended");
@@ -250,7 +259,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
}
private boolean isReorderingAllowed() {
- return ((mFullyDozed && mSleepy) || !mPanelExpanded || mCommunalShowing) && !mPulsing;
+ final boolean sleepyAndDozed = mFullyDozed && mSleepy;
+ final boolean stackShowing = mPanelExpanded || mLockscreenShowing;
+ return (sleepyAndDozed || !stackShowing || mCommunalShowing) && !mPulsing;
}
/**
@@ -363,4 +374,14 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
mCommunalShowing = isShowing;
updateAllowedStates("communalShowing", isShowing);
}
+
+ private void onLockscreenKeyguardStateTransitionValueChanged(float value) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ final boolean isShowing = value > 0.0f;
+ mLockscreenShowing = isShowing;
+ updateAllowedStates("lockscreenShowing", isShowing);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
new file mode 100644
index 000000000000..fe23e4e12db4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 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.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VisualStabilityLog
+import javax.inject.Inject
+
+private const val TAG = "VisualStability"
+
+class VisualStabilityCoordinatorLogger
+@Inject
+constructor(@VisualStabilityLog private val buffer: LogBuffer) {
+ fun logAllowancesChanged(
+ wasRunAllowed: Boolean,
+ isRunAllowed: Boolean,
+ wasReorderingAllowed: Boolean,
+ isReorderingAllowed: Boolean,
+ field: String,
+ value: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = wasRunAllowed
+ bool2 = isRunAllowed
+ bool3 = wasReorderingAllowed
+ bool4 = isReorderingAllowed
+ str1 = field
+ str2 = value.toString()
+ },
+ {
+ "stability allowances changed:" +
+ " pipelineRunAllowed $bool1->$bool2" +
+ " reorderingAllowed $bool3->$bool4" +
+ " when setting $str1=$str2"
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
index 5adf31b75fa7..5614f3fc9590 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
@@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification.collection.provider
import android.util.ArraySet
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.util.ListenerSet
import javax.inject.Inject
@@ -13,12 +14,18 @@ class VisualStabilityProvider @Inject constructor() {
/** The subset of active listeners which are temporary (will be removed after called) */
private val temporaryListeners = ArraySet<OnReorderingAllowedListener>()
+ private val banListeners = ListenerSet<OnReorderingBannedListener>()
+
var isReorderingAllowed = true
set(value) {
if (field != value) {
field = value
if (value) {
notifyReorderingAllowed()
+ } else if (NotificationThrottleHun.isEnabled){
+ banListeners.forEach { listener ->
+ listener.onReorderingBanned()
+ }
}
}
}
@@ -38,6 +45,10 @@ class VisualStabilityProvider @Inject constructor() {
allListeners.addIfAbsent(listener)
}
+ fun addPersistentReorderingBannedListener(listener: OnReorderingBannedListener) {
+ banListeners.addIfAbsent(listener)
+ }
+
/** Add a listener which will be removed when it is called. */
fun addTemporaryReorderingAllowedListener(listener: OnReorderingAllowedListener) {
// Only add to the temporary set if it was added to the global set
@@ -57,3 +68,7 @@ class VisualStabilityProvider @Inject constructor() {
fun interface OnReorderingAllowedListener {
fun onReorderingAllowed()
}
+
+fun interface OnReorderingBannedListener {
+ fun onReorderingBanned()
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index bf44b9f3cf78..24b75d49ed16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -30,6 +30,7 @@ import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -44,8 +45,17 @@ constructor(
private val shadeInteractor: ShadeInteractor,
) {
+ /** The top-ranked heads up row, regardless of pinned state */
val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
+ /** The top-ranked heads up row, if that row is pinned */
+ val topHeadsUpRowIfPinned: Flow<HeadsUpRowKey?> =
+ headsUpRepository.topHeadsUpRow
+ .flatMapLatest { repository ->
+ repository?.isPinned?.map { pinned -> repository.takeIf { pinned } } ?: flowOf(null)
+ }
+ .distinctUntilChanged()
+
/** Set of currently pinned top-level heads up rows to be displayed. */
val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
@@ -89,10 +99,10 @@ constructor(
flowOf(false)
} else {
combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
- hasPinnedRows,
- animatingAway ->
- hasPinnedRows || animatingAway
- }
+ hasPinnedRows,
+ animatingAway ->
+ hasPinnedRows || animatingAway
+ }
}
}
@@ -127,6 +137,9 @@ constructor(
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+ /** Returns the Notification Key (the standard string) of this row. */
+ fun notificationKey(key: HeadsUpRowKey): String = (key as HeadsUpRowRepository).key
+
fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 85c66bd6f25a..948a3c2f65b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.util.IndentingPrintWriter
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.util.printSection
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
@@ -41,24 +43,42 @@ constructor(
/** Set the entry that is identified as the top ongoing notification. */
fun setTopOngoingNotification(entry: NotificationEntry?) {
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
notificationListRepository.topOngoingNotificationKey.value = entry?.key
}
/** Determine if the given notification is the top ongoing notification. */
fun isTopOngoingNotification(entry: NotificationEntry?): Boolean =
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) false
else
entry != null && notificationListRepository.topOngoingNotificationKey.value == entry.key
/** Set the entry that is identified as the top unseen notification. */
fun setTopUnseenNotification(entry: NotificationEntry?) {
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
notificationListRepository.topUnseenNotificationKey.value = entry?.key
}
/** Determine if the given notification is the top unseen notification. */
fun isTopUnseenNotification(entry: NotificationEntry?): Boolean =
- if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+ if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) false
else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key
+
+ fun dump(pw: IndentingPrintWriter) =
+ with(pw) {
+ printSection("SeenNotificationsInteractor") {
+ print(
+ "hasFilteredOutSeenNotifications",
+ notificationListRepository.hasFilteredOutSeenNotifications.value
+ )
+ print(
+ "topOngoingNotificationKey",
+ notificationListRepository.topOngoingNotificationKey.value
+ )
+ print(
+ "topUnseenNotificationKey",
+ notificationListRepository.topUnseenNotificationKey.value
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index b8af3698fb63..fe86375d628e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -122,12 +122,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
val timeRemaining = parseTimeDelta(remaining)
TimerContentModel(
icon = icon,
- name = total,
+ // TODO: b/352142761 - define and use a string resource rather than " Timer".
+ // (The UX isn't final so using " Timer" for now).
+ name = total.replace("Σ", "") + " Timer",
state =
TimerContentModel.TimerState.Paused(
timeRemaining = timeRemaining,
- resumeIntent = notification.findActionWithName("Resume"),
- resetIntent = notification.findActionWithName("Reset"),
+ resumeIntent = notification.findStartIntent(),
+ addMinuteAction = notification.findAddMinuteAction(),
+ resetAction = notification.findResetAction(),
)
)
}
@@ -136,12 +139,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis()
TimerContentModel(
icon = icon,
- name = total,
+ // TODO: b/352142761 - define and use a string resource rather than " Timer".
+ // (The UX isn't final so using " Timer" for now).
+ name = total.replace("Σ", "") + " Timer",
state =
TimerContentModel.TimerState.Running(
finishTime = finishTime,
- pauseIntent = notification.findActionWithName("Pause"),
- addOneMinuteIntent = notification.findActionWithName("Add 1 min"),
+ pauseIntent = notification.findPauseIntent(),
+ addMinuteAction = notification.findAddMinuteAction(),
+ resetAction = notification.findResetAction(),
)
)
}
@@ -149,8 +155,34 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
}
}
- private fun Notification.findActionWithName(name: String): PendingIntent? {
- return actions.firstOrNull { name == it.title?.toString() }?.actionIntent
+ private fun Notification.findPauseIntent(): PendingIntent? {
+ return actions
+ .firstOrNull { it.actionIntent.intent?.action?.endsWith(".PAUSE_TIMER") == true }
+ ?.actionIntent
+ }
+
+ private fun Notification.findStartIntent(): PendingIntent? {
+ return actions
+ .firstOrNull { it.actionIntent.intent?.action?.endsWith(".START_TIMER") == true }
+ ?.actionIntent
+ }
+
+ // TODO: b/352142761 - switch to system attributes for label and icon.
+ // - We probably want a consistent look for the Reset button. (Double check with UX.)
+ // - Using the custom assets now since I couldn't an existing "Reset" icon.
+ private fun Notification.findResetAction(): Notification.Action? {
+ return actions.firstOrNull {
+ it.actionIntent.intent?.action?.endsWith(".RESET_TIMER") == true
+ }
+ }
+
+ // TODO: b/352142761 - check with UX on whether this should be required.
+ // - Alternative is to allow for optional actions in addition to main and reset.
+ // - For optional actions, we should take the custom label and icon.
+ private fun Notification.findAddMinuteAction(): Notification.Action? {
+ return actions.firstOrNull {
+ it.actionIntent.intent?.action?.endsWith(".ADD_MINUTE_TIMER") == true
+ }
}
private fun parseCurrentTime(current: String): Long {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
index 558470175e8d..33b256456ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row.shared
+import android.app.Notification
import android.app.PendingIntent
import java.time.Duration
@@ -32,6 +33,9 @@ data class TimerContentModel(
) : RichOngoingContentModel {
/** The state (paused or running) of the timer, and relevant time */
sealed interface TimerState {
+ val addMinuteAction: Notification.Action?
+ val resetAction: Notification.Action?
+
/**
* Indicates a running timer
*
@@ -41,7 +45,8 @@ data class TimerContentModel(
data class Running(
val finishTime: Long,
val pauseIntent: PendingIntent?,
- val addOneMinuteIntent: PendingIntent?,
+ override val addMinuteAction: Notification.Action?,
+ override val resetAction: Notification.Action?,
) : TimerState
/**
@@ -53,7 +58,8 @@ data class TimerContentModel(
data class Paused(
val timeRemaining: Duration,
val resumeIntent: PendingIntent?,
- val resetIntent: PendingIntent?,
+ override val addMinuteAction: Notification.Action?,
+ override val resetAction: Notification.Action?,
) : TimerState
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
index 0d83aced6d07..8c951877544c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
@@ -18,8 +18,9 @@ package com.android.systemui.statusbar.notification.row.ui.view
import android.annotation.DrawableRes
import android.content.Context
+import android.graphics.BlendMode
import android.util.AttributeSet
-import android.widget.Button
+import com.android.internal.widget.EmphasizedNotificationButton
class TimerButtonView
@JvmOverloads
@@ -28,14 +29,19 @@ constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
-) : Button(context, attrs, defStyleAttr, defStyleRes) {
+) : EmphasizedNotificationButton(context, attrs, defStyleAttr, defStyleRes) {
private val Int.dp: Int
get() = (this * context.resources.displayMetrics.density).toInt()
fun setIcon(@DrawableRes icon: Int) {
val drawable = context.getDrawable(icon)
+
+ drawable?.mutate()
+ drawable?.setTintList(textColors)
+ drawable?.setTintBlendMode(BlendMode.SRC_IN)
drawable?.setBounds(0, 0, 24.dp, 24.dp)
+
setCompoundDrawablesRelative(drawable, null, null, null)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
index 2e164d60431d..d481b50101c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.row.ui.view
import android.content.Context
-import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
import android.os.SystemClock
import android.util.AttributeSet
import android.widget.Chronometer
@@ -48,6 +48,9 @@ constructor(
lateinit var altButton: TimerButtonView
private set
+ lateinit var resetButton: TimerButtonView
+ private set
+
override fun onFinishInflate() {
super.onFinishInflate()
icon = requireViewById(R.id.icon)
@@ -56,13 +59,14 @@ constructor(
pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining)
mainButton = requireViewById(R.id.mainButton)
altButton = requireViewById(R.id.altButton)
+ resetButton = requireViewById(R.id.resetButton)
}
/** the resources configuration has changed such that the view needs to be reinflated */
fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
- fun setIcon(iconDrawable: Drawable?) {
- this.icon.setImageDrawable(iconDrawable)
+ fun setIcon(icon: Icon?) {
+ this.icon.setImageIcon(icon)
}
fun setLabel(label: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
index c9ff58961582..042d1bcfb2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row.ui.viewbinder
+import android.content.res.ColorStateList
+import android.graphics.drawable.Icon
import android.view.View
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
@@ -46,12 +48,43 @@ object TimerViewBinder {
launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } }
launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } }
launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } }
+ launch { viewModel.resetButtonModel.collect { bind(view.resetButton, it) } }
}
fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) {
if (model != null) {
- buttonView.setIcon(model.iconRes)
- buttonView.setText(model.labelRes)
+ buttonView.setButtonBackground(
+ ColorStateList.valueOf(
+ buttonView.context.getColor(com.android.internal.R.color.system_accent2_100)
+ )
+ )
+ buttonView.setTextColor(
+ buttonView.context.getColor(
+ com.android.internal.R.color.notification_primary_text_color_light
+ )
+ )
+
+ when (model) {
+ is TimerViewModel.ButtonViewModel.WithSystemAttrs -> {
+ buttonView.setIcon(model.iconRes)
+ buttonView.setText(model.labelRes)
+ }
+ is TimerViewModel.ButtonViewModel.WithCustomAttrs -> {
+ // TODO: b/352142761 - is there a better way to deal with TYPE_RESOURCE icons
+ // with empty resPackage? RemoteViews handles this by using a different
+ // `contextForResources` for inflation.
+ val icon =
+ if (model.icon.type == Icon.TYPE_RESOURCE && model.icon.resPackage == "")
+ Icon.createWithResource(
+ "com.google.android.deskclock",
+ model.icon.resId
+ )
+ else model.icon
+ buttonView.setImageIcon(icon)
+ buttonView.text = model.label
+ }
+ }
+
buttonView.setOnClickListener(
model.pendingIntent?.let { pendingIntent ->
View.OnClickListener { pendingIntent.send() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
index a85c87f288d3..768a093e0b65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.row.ui.viewmodel
import android.annotation.DrawableRes
import android.annotation.StringRes
import android.app.PendingIntent
-import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
@@ -44,7 +44,7 @@ constructor(
private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state }
- val icon: Flow<Drawable?> = rowInteractor.timerContentModel.mapNotNull { it.icon.drawable }
+ val icon: Flow<Icon?> = rowInteractor.timerContentModel.mapNotNull { it.icon.icon }
val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name }
@@ -57,13 +57,13 @@ constructor(
state.map {
when (it) {
is TimerState.Paused ->
- ButtonViewModel(
+ ButtonViewModel.WithSystemAttrs(
it.resumeIntent,
com.android.systemui.res.R.string.controls_media_resume, // "Resume",
com.android.systemui.res.R.drawable.ic_media_play
)
is TimerState.Running ->
- ButtonViewModel(
+ ButtonViewModel.WithSystemAttrs(
it.pauseIntent,
com.android.systemui.res.R.string.controls_media_button_pause, // "Pause",
com.android.systemui.res.R.drawable.ic_media_pause
@@ -73,31 +73,41 @@ constructor(
val altButtonModel: Flow<ButtonViewModel?> =
state.map {
- when (it) {
- is TimerState.Paused ->
- it.resetIntent?.let { resetIntent ->
- ButtonViewModel(
- resetIntent,
- com.android.systemui.res.R.string.reset, // "Reset",
- com.android.systemui.res.R.drawable.ic_close_white_rounded
- )
- }
- is TimerState.Running ->
- it.addOneMinuteIntent?.let { addOneMinuteIntent ->
- ButtonViewModel(
- addOneMinuteIntent,
- com.android.systemui.res.R.string.add, // "Add 1 minute",
- com.android.systemui.res.R.drawable.ic_add
- )
- }
+ it.addMinuteAction?.let { action ->
+ ButtonViewModel.WithCustomAttrs(
+ action.actionIntent,
+ action.title, // "1:00",
+ action.getIcon()
+ )
+ }
+ }
+
+ val resetButtonModel: Flow<ButtonViewModel?> =
+ state.map {
+ it.resetAction?.let { action ->
+ ButtonViewModel.WithCustomAttrs(
+ action.actionIntent,
+ action.title, // "Reset",
+ action.getIcon()
+ )
}
}
- data class ButtonViewModel(
- val pendingIntent: PendingIntent?,
- @StringRes val labelRes: Int,
- @DrawableRes val iconRes: Int,
- )
+ sealed interface ButtonViewModel {
+ val pendingIntent: PendingIntent?
+
+ data class WithSystemAttrs(
+ override val pendingIntent: PendingIntent?,
+ @StringRes val labelRes: Int,
+ @DrawableRes val iconRes: Int,
+ ) : ButtonViewModel
+
+ data class WithCustomAttrs(
+ override val pendingIntent: PendingIntent?,
+ val label: CharSequence,
+ val icon: Icon,
+ ) : ButtonViewModel
+ }
}
private fun Duration.format(): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
index bf37036ee018..06f3db504aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
@@ -24,102 +24,43 @@ import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the minimalism prototype flag state. */
@Suppress("NOTHING_TO_INLINE")
object NotificationMinimalismPrototype {
-
- val version: Int by lazy {
- SystemProperties.getInt("persist.notification_minimalism_prototype.version", 2)
- }
-
- object V1 {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the heads-up cycling animation enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.notificationMinimalismPrototype() && version == 1
-
- /**
- * the prototype will now show seen notifications on the locked shade by default, but this
- * property read allows that to be quickly disabled for testing
- */
- val showOnLockedShade: Boolean
- get() =
- if (isUnexpectedlyInLegacyMode()) false
- else
- SystemProperties.getBoolean(
- "persist.notification_minimalism_prototype.show_on_locked_shade",
- true
- )
-
- /** gets the configurable max number of notifications */
- val maxNotifs: Int
- get() =
- if (isUnexpectedlyInLegacyMode()) -1
- else
- SystemProperties.getInt(
- "persist.notification_minimalism_prototype.lock_screen_max_notifs",
- 1
- )
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an
- * eng build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception
- * if the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
- }
- object V2 {
- const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the heads-up cycling animation enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.notificationMinimalismPrototype() && version == 2
-
- /**
- * The prototype will (by default) use a promoter to ensure that the top unseen notification
- * is not grouped, but this property read allows that behavior to be disabled.
- */
- val ungroupTopUnseen: Boolean
- get() =
- if (isUnexpectedlyInLegacyMode()) false
- else
- SystemProperties.getBoolean(
- "persist.notification_minimalism_prototype.ungroup_top_unseen",
- true
- )
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an
- * eng build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception
- * if the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
- }
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the heads-up cycling animation enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationMinimalismPrototype()
+
+ /**
+ * The prototype will (by default) use a promoter to ensure that the top unseen notification is
+ * not grouped, but this property read allows that behavior to be disabled.
+ */
+ val ungroupTopUnseen: Boolean
+ get() =
+ if (isUnexpectedlyInLegacyMode()) false
+ else
+ SystemProperties.getBoolean(
+ "persist.notification_minimalism_prototype.ungroup_top_unseen",
+ false
+ )
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index cec1ef3f1e7b..5d2b61b42db9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -111,6 +111,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -4862,14 +4863,23 @@ public class NotificationStackScrollLayout
* @param isHeadsUp true for appear, false for disappear animations
*/
public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
- final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
+ boolean addAnimation =
+ mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
+ if (NotificationThrottleHun.isEnabled()) {
+ final boolean closedAndSeenInShade = !mIsExpanded && row.getEntry() != null
+ && row.getEntry().isSeenInShade();
+ addAnimation = addAnimation && !closedAndSeenInShade;
+ }
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation:"
- + " willAdd=" + add
- + " isHeadsUp=" + isHeadsUp
- + " row=" + row.getEntry().getKey());
- }
- if (add) {
+ + " addAnimation=" + addAnimation
+ + (row.getEntry() == null ? " entry NULL "
+ : " isSeenInShade=" + row.getEntry().isSeenInShade()
+ + " row=" + row.getEntry().getKey())
+ + " mIsExpanded=" + mIsExpanded
+ + " isHeadsUp=" + isHeadsUp);
+ }
+ if (addAnimation) {
// If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
// and do not add the disappear event either.
if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 391bc43a784c..06222fdb2761 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -74,7 +74,7 @@ constructor(
/** Whether we allow keyguard to show less important notifications above the shelf. */
private val limitLockScreenToOneImportant
- get() = NotificationMinimalismPrototype.V2.isEnabled
+ get() = NotificationMinimalismPrototype.isEnabled
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
@@ -405,16 +405,8 @@ constructor(
fun updateResources() {
maxKeyguardNotifications =
- infiniteIfNegative(
- if (NotificationMinimalismPrototype.V1.isEnabled) {
- NotificationMinimalismPrototype.V1.maxNotifs
- } else {
- resources.getInteger(R.integer.keyguard_max_notification_count)
- }
- )
- maxNotificationsExcludesMedia =
- NotificationMinimalismPrototype.V1.isEnabled ||
- NotificationMinimalismPrototype.V2.isEnabled
+ infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
+ maxNotificationsExcludesMedia = NotificationMinimalismPrototype.isEnabled
dividerHeight =
max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 97266c57ba09..86c7c6bd1e86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -24,6 +24,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -36,10 +37,16 @@ class ActivityStarterImpl
constructor(
private val statusBarStateController: SysuiStatusBarStateController,
@Main private val mainExecutor: DelayableExecutor,
+ activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
) : ActivityStarter {
- private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get()
+ private val activityStarterInternal: ActivityStarterInternal =
+ if (SceneContainerFlag.isEnabled) {
+ activityStarterInternal.get()
+ } else {
+ legacyActivityStarter.get()
+ }
override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
activityStarterInternal.startPendingIntentDismissingKeyguard(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index ae98e1d60589..107bf1edb74f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -16,23 +16,93 @@
package com.android.systemui.statusbar.phone
+import android.app.ActivityManager
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
import android.app.PendingIntent
+import android.app.TaskStackBuilder
+import android.content.Context
import android.content.Intent
+import android.content.res.Resources
import android.os.Bundle
+import android.os.RemoteException
import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import android.view.RemoteAnimationAdapter
import android.view.View
+import android.view.WindowManager
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.Flags
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Lazy
+import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/**
- * Encapsulates the activity logic for activity starter when flexiglass is enabled.
+ * Encapsulates the activity logic for activity starter when the SceneContainerFlag is enabled.
*
* TODO: b/308819693
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
-class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInternal {
+class ActivityStarterInternalImpl
+@Inject
+constructor(
+ private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
+ private val context: Context,
+ @Main private val resources: Resources,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
+ @DisplayId private val displayId: Int,
+ private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+ private val activityIntentHelper: ActivityIntentHelper,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val assistManagerLazy: Lazy<AssistManager>,
+ @Main private val mainExecutor: DelayableExecutor,
+ private val shadeControllerLazy: Lazy<ShadeController>,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+ private val shadeAnimationInteractor: ShadeAnimationInteractor,
+ private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
+ private val commandQueue: CommandQueue,
+ private val lockScreenUserManager: NotificationLockscreenUserManager,
+) : ActivityStarterInternal {
+ private val centralSurfaces: CentralSurfaces?
+ get() = centralSurfacesOptLazy.get().getOrNull()
+
override fun startPendingIntentDismissingKeyguard(
intent: PendingIntent,
dismissShade: Boolean,
@@ -45,7 +115,119 @@ class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInterna
extraOptions: Bundle?,
customMessage: String?,
) {
- TODO("Not yet implemented b/308819693")
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+ val animationController =
+ if (associatedView is ExpandableNotificationRow) {
+ centralSurfaces?.getAnimatorControllerFromNotification(associatedView)
+ } else animationController
+
+ val willLaunchResolverActivity =
+ intent.isActivity &&
+ activityIntentHelper.wouldPendingLaunchResolverActivity(
+ intent,
+ lockScreenUserManager.currentUserId,
+ )
+
+ val actuallyShowOverLockscreen =
+ showOverLockscreen &&
+ intent.isActivity &&
+ (skipLockscreenChecks ||
+ activityIntentHelper.wouldPendingShowOverLockscreen(
+ intent,
+ lockScreenUserManager.currentUserId
+ ))
+
+ val animate =
+ !willLaunchResolverActivity &&
+ animationController != null &&
+ shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+ // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+ // that the shade is collapsed after the animation (or when it is cancelled,
+ // aborted, etc).
+ val statusBarController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = dismissShade,
+ isLaunchForActivity = intent.isActivity,
+ )
+ val controller =
+ if (actuallyShowOverLockscreen) {
+ wrapAnimationControllerForLockscreen(dismissShade, statusBarController)
+ } else {
+ statusBarController
+ }
+
+ // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
+ // run the animation on the keyguard). The animation will take care of (instantly)
+ // collapsing the shade and hiding the keyguard once it is done.
+ val collapse = dismissShade && !animate
+ val runnable = Runnable {
+ try {
+ activityTransitionAnimator.startPendingIntentWithAnimation(
+ controller,
+ animate,
+ intent.creatorPackage,
+ actuallyShowOverLockscreen,
+ object : ActivityTransitionAnimator.PendingIntentStarter {
+ override fun startPendingIntent(
+ animationAdapter: RemoteAnimationAdapter?
+ ): Int {
+ val options =
+ ActivityOptions(
+ CentralSurfaces.getActivityOptions(displayId, animationAdapter)
+ .apply { extraOptions?.let { putAll(it) } }
+ )
+ // TODO b/221255671: restrict this to only be set for notifications
+ options.isEligibleForLegacyPermissionPrompt = true
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ return intent.sendAndReturnResult(
+ context,
+ 0,
+ fillInIntent,
+ null,
+ null,
+ null,
+ options.toBundle()
+ )
+ }
+ },
+ )
+ } catch (e: PendingIntent.CanceledException) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: $e")
+ if (!collapse) {
+ // executeRunnableDismissingKeyguard did not collapse for us already.
+ shadeControllerLazy.get().collapseOnMainThread()
+ }
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity) {
+ assistManagerLazy.get().hideAssist()
+ // This activity could have started while the device is dreaming, in which case
+ // the dream would occlude the activity. In order to show the newly started
+ // activity, we wake from the dream.
+ centralSurfaces?.awakenDreams()
+ }
+ intentSentUiThreadCallback?.let { mainExecutor.execute(it) }
+ }
+
+ if (!actuallyShowOverLockscreen) {
+ mainExecutor.execute {
+ executeRunnableDismissingKeyguard(
+ runnable = runnable,
+ afterKeyguardGone = willLaunchResolverActivity,
+ dismissShade = collapse,
+ willAnimateOnKeyguard = animate,
+ customMessage = customMessage,
+ )
+ }
+ } else {
+ mainExecutor.execute(runnable)
+ }
}
override fun startActivityDismissingKeyguard(
@@ -59,7 +241,116 @@ class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInterna
disallowEnterPictureInPictureWhileLaunching: Boolean,
userHandle: UserHandle?
) {
- TODO("Not yet implemented b/308819693")
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+ val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
+
+ if (onlyProvisioned && !deviceProvisioningInteractor.isDeviceProvisioned()) return
+
+ val willLaunchResolverActivity: Boolean =
+ activityIntentHelper.wouldLaunchResolverActivity(
+ intent,
+ selectedUserInteractor.getSelectedUserId(),
+ )
+
+ val animate =
+ animationController != null &&
+ !willLaunchResolverActivity &&
+ shouldAnimateLaunch(isActivityIntent = true)
+ val animController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = dismissShade,
+ isLaunchForActivity = true,
+ )
+
+ // If we animate, we will dismiss the shade only once the animation is done. This is
+ // taken care of by the StatusBarLaunchAnimationController.
+ val dismissShadeDirectly = dismissShade && animController == null
+
+ val runnable = Runnable {
+ assistManagerLazy.get().hideAssist()
+ intent.flags =
+ if (intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT != 0) {
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ } else {
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
+ intent.addFlags(flags)
+ val result = intArrayOf(ActivityManager.START_CANCELED)
+ activityTransitionAnimator.startIntentWithAnimation(
+ animController,
+ animate,
+ intent.getPackage()
+ ) { adapter: RemoteAnimationAdapter? ->
+ val options =
+ ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
+
+ // We know that the intent of the caller is to dismiss the keyguard and
+ // this runnable is called right after the keyguard is solved, so we tell
+ // WM that we should dismiss it to avoid flickers when opening an activity
+ // that can also be shown over the keyguard.
+ options.setDismissKeyguardIfInsecure()
+ options.setDisallowEnterPictureInPictureWhileLaunching(
+ disallowEnterPictureInPictureWhileLaunching
+ )
+ if (CameraIntents.isInsecureCameraIntent(intent)) {
+ // Normally an activity will set it's requested rotation
+ // animation on its window. However when launching an activity
+ // causes the orientation to change this is too late. In these cases
+ // the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync
+ // with physical reality). So, we ask the WindowManager to
+ // force the cross fade animation if an orientation change
+ // happens to occur during the launch.
+ options.rotationAnimationHint =
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ }
+ if (Settings.Panel.ACTION_VOLUME == intent.action) {
+ // Settings Panel is implemented as activity(not a dialog), so
+ // underlying app is paused and may enter picture-in-picture mode
+ // as a result.
+ // So we need to disable picture-in-picture mode here
+ // if it is volume panel.
+ options.setDisallowEnterPictureInPictureWhileLaunching(true)
+ }
+ try {
+ result[0] =
+ ActivityTaskManager.getService()
+ .startActivityAsUser(
+ null,
+ context.basePackageName,
+ context.attributionTag,
+ intent,
+ intent.resolveTypeIfNeeded(context.contentResolver),
+ null,
+ null,
+ 0,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ null,
+ options.toBundle(),
+ userHandle.identifier,
+ )
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Unable to start activity", e)
+ }
+ result[0]
+ }
+ callback?.onActivityStarted(result[0])
+ }
+ val cancelRunnable = Runnable {
+ callback?.onActivityStarted(ActivityManager.START_CANCELED)
+ }
+ // Do not deferKeyguard when occluded because, when keyguard is occluded,
+ // we do not launch the activity until keyguard is done.
+ executeRunnableDismissingKeyguard(
+ runnable,
+ cancelRunnable,
+ dismissShadeDirectly,
+ willLaunchResolverActivity,
+ deferred = !isKeyguardOccluded(),
+ animate,
+ customMessage,
+ )
}
override fun startActivity(
@@ -69,7 +360,64 @@ class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInterna
showOverLockscreenWhenLocked: Boolean,
userHandle: UserHandle?
) {
- TODO("Not yet implemented b/308819693")
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+ val userHandle = userHandle ?: getActivityUserHandle(intent)
+ // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't
+ // want to show the activity above it.
+ if (deviceEntryInteractor.isUnlocked.value || !showOverLockscreenWhenLocked) {
+ startActivityDismissingKeyguard(
+ intent = intent,
+ onlyProvisioned = false,
+ dismissShade = dismissShade,
+ disallowEnterPictureInPictureWhileLaunching = false,
+ callback = null,
+ flags = 0,
+ animationController = animationController,
+ userHandle = userHandle,
+ )
+ return
+ }
+
+ val animate =
+ animationController != null &&
+ shouldAnimateLaunch(
+ isActivityIntent = true,
+ showOverLockscreen = showOverLockscreenWhenLocked
+ )
+
+ var controller: ActivityTransitionAnimator.Controller? = null
+ if (animate) {
+ // Wrap the animation controller to dismiss the shade and set
+ // mIsLaunchingActivityOverLockscreen during the animation.
+ val delegate =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = dismissShade,
+ isLaunchForActivity = true,
+ )
+ controller = wrapAnimationControllerForLockscreen(dismissShade, delegate)
+ } else if (dismissShade) {
+ // The animation will take care of dismissing the shade at the end of the animation.
+ // If we don't animate, collapse it directly.
+ shadeControllerLazy.get().cancelExpansionAndCollapseShade()
+ }
+
+ // We should exit the dream to prevent the activity from starting below the
+ // dream.
+ if (keyguardInteractor.isDreaming.value) {
+ centralSurfaces?.awakenDreams()
+ }
+
+ activityTransitionAnimator.startIntentWithAnimation(
+ controller,
+ animate,
+ intent.getPackage(),
+ showOverLockscreenWhenLocked
+ ) { adapter: RemoteAnimationAdapter? ->
+ TaskStackBuilder.create(context)
+ .addNextIntent(intent)
+ .startActivities(CentralSurfaces.getActivityOptions(displayId, adapter), userHandle)
+ }
}
override fun dismissKeyguardThenExecute(
@@ -78,7 +426,23 @@ class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInterna
afterKeyguardGone: Boolean,
customMessage: String?
) {
- TODO("Not yet implemented b/308819693")
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+ Log.i(TAG, "Invoking dismissKeyguardThenExecute, afterKeyguardGone: $afterKeyguardGone")
+
+ // TODO b/308819693: startWakeAndUnlock animation when pulsing
+
+ if (isKeyguardShowing()) {
+ statusBarKeyguardViewManagerLazy
+ .get()
+ .dismissWithAction(action, cancel, afterKeyguardGone, customMessage)
+ } else {
+ // If the keyguard isn't showing but the device is dreaming, we should exit the
+ // dream.
+ if (keyguardInteractor.isDreaming.value) {
+ centralSurfaces?.awakenDreams()
+ }
+ action.onDismiss()
+ }
}
override fun executeRunnableDismissingKeyguard(
@@ -90,10 +454,195 @@ class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInterna
willAnimateOnKeyguard: Boolean,
customMessage: String?
) {
- TODO("Not yet implemented b/308819693")
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
+ val onDismissAction: ActivityStarter.OnDismissAction =
+ object : ActivityStarter.OnDismissAction {
+ override fun onDismiss(): Boolean {
+ if (runnable != null) {
+ if (isKeyguardOccluded()) {
+ statusBarKeyguardViewManagerLazy
+ .get()
+ .addAfterKeyguardGoneRunnable(runnable)
+ } else {
+ mainExecutor.execute(runnable)
+ }
+ }
+ if (dismissShade) {
+ shadeControllerLazy.get().collapseShadeForActivityStart()
+ }
+ if (Flags.communalHub()) {
+ communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
+ }
+ return deferred
+ }
+
+ override fun willRunAnimationOnKeyguard(): Boolean {
+ if (Flags.communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
+ // Override to false when launching activity over the hub that requires auth
+ return false
+ }
+ return willAnimateOnKeyguard
+ }
+ }
+ dismissKeyguardThenExecute(
+ onDismissAction,
+ cancelAction,
+ afterKeyguardGone,
+ customMessage,
+ )
}
override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
- TODO("Not yet implemented b/308819693")
+ return shouldAnimateLaunch(isActivityIntent, false)
+ }
+
+ /**
+ * Whether we should animate an activity launch.
+ *
+ * Note: This method must be called *before* dismissing the keyguard.
+ */
+ private fun shouldAnimateLaunch(
+ isActivityIntent: Boolean,
+ showOverLockscreen: Boolean,
+ ): Boolean {
+ // TODO(b/294418322): always support launch animations when occluded.
+ val ignoreOcclusion = showOverLockscreen && Flags.mediaLockscreenLaunchAnimation()
+ if (isKeyguardOccluded() && !ignoreOcclusion) {
+ return false
+ }
+
+ // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+ // (without unlocking it).
+ if (showOverLockscreen || !isKeyguardShowing()) {
+ return true
+ }
+
+ // We don't animate non-activity launches as they can break the animation.
+ // TODO(b/184121838): Support non activity launches on the lockscreen.
+ return isActivityIntent
+ }
+
+ /** Retrieves the current user handle to start the Activity. */
+ private fun getActivityUserHandle(intent: Intent): UserHandle {
+ val packages: Array<String> = resources.getStringArray(R.array.system_ui_packages)
+ for (pkg in packages) {
+ val componentName = intent.component ?: break
+ if (pkg == componentName.packageName) {
+ return UserHandle(UserHandle.myUserId())
+ }
+ }
+ return UserHandle(selectedUserInteractor.getSelectedUserId())
+ }
+
+ private fun isKeyguardShowing(): Boolean {
+ return !deviceEntryInteractor.isDeviceEntered.value
+ }
+
+ private fun isKeyguardOccluded(): Boolean {
+ return keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
+ }
+
+ /**
+ * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that:
+ * - if it launches in the notification shade window and `dismissShade` is true, then the shade
+ * will be instantly dismissed at the end of the animation.
+ * - if it launches in status bar window, it will make the status bar window match the device
+ * size during the animation (that way, the animation won't be clipped by the status bar
+ * size).
+ *
+ * @param animationController the controller that is wrapped and will drive the main animation.
+ * @param dismissShade whether the notification shade will be dismissed at the end of the
+ * animation. This is ignored if `animationController` is not animating in the shade window.
+ * @param isLaunchForActivity whether the launch is for an activity.
+ */
+ private fun wrapAnimationControllerForShadeOrStatusBar(
+ animationController: ActivityTransitionAnimator.Controller?,
+ dismissShade: Boolean,
+ isLaunchForActivity: Boolean,
+ ): ActivityTransitionAnimator.Controller? {
+ if (animationController == null) {
+ return null
+ }
+ val rootView = animationController.transitionContainer.rootView
+ val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
+ statusBarWindowController.wrapAnimationControllerIfInStatusBar(
+ rootView,
+ animationController
+ )
+ if (controllerFromStatusBar.isPresent) {
+ return controllerFromStatusBar.get()
+ }
+
+ centralSurfaces?.let {
+ // If the view is not in the status bar, then we are animating a view in the shade.
+ // We have to make sure that we collapse it when the animation ends or is cancelled.
+ if (dismissShade) {
+ return StatusBarTransitionAnimatorController(
+ animationController,
+ shadeAnimationInteractor,
+ shadeControllerLazy.get(),
+ notifShadeWindowControllerLazy.get(),
+ commandQueue,
+ displayId,
+ isLaunchForActivity
+ )
+ }
+ }
+
+ return animationController
+ }
+
+ /**
+ * Wraps an animation controller so that if an activity would be launched on top of the
+ * lockscreen, the correct flags are set for it to be occluded.
+ */
+ private fun wrapAnimationControllerForLockscreen(
+ dismissShade: Boolean,
+ animationController: ActivityTransitionAnimator.Controller?
+ ): ActivityTransitionAnimator.Controller? {
+ return animationController?.let {
+ object : DelegateTransitionAnimatorController(it) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ delegate.onIntentStarted(willAnimate)
+ if (willAnimate) {
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(true, dismissShade)
+ }
+ }
+
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ super.onTransitionAnimationStart(isExpandingFullyAbove)
+ if (Flags.communalHub()) {
+ communalSceneInteractor.snapToScene(
+ CommunalScenes.Blank,
+ ActivityTransitionAnimator.TIMINGS.totalDuration
+ )
+ }
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the post collapse runnables)
+ // later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the // post collapse
+ // runnables) later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "ActivityStarterInternalImpl"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a2e44dffb767..e08dbb9df7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -39,11 +39,13 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
@@ -86,7 +88,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
private final VisualStabilityProvider mVisualStabilityProvider;
- private final AvalancheController mAvalancheController;
+ private AvalancheController mAvalancheController;
// TODO(b/328393698) move the topHeadsUpRow logic to an interactor
private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
@@ -173,6 +175,12 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
});
javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
this::onShadeOrQsExpanded);
+ if (NotificationThrottleHun.isEnabled()) {
+ mVisualStabilityProvider.addPersistentReorderingBannedListener(
+ mOnReorderingBannedListener);
+ mVisualStabilityProvider.addPersistentReorderingAllowedListener(
+ mOnReorderingAllowedListener);
+ }
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -300,6 +308,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
+ if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
+ headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
+ }
if (remoteInputActive) {
headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
} else {
@@ -379,6 +390,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+ if (NotificationThrottleHun.isEnabled()) {
+ mAvalancheController.setEnableAtRuntime(true);
+ }
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
@@ -389,6 +403,22 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
};
+ private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
+ if (mAvalancheController != null) {
+ // In open shade the first HUN is pinned, and visual stability logic prevents us from
+ // unpinning this first HUN as long as the shade remains open. AvalancheController only
+ // shows the next HUN when the currently showing HUN is unpinned, so we must disable
+ // throttling here so that the incoming HUN stream is not forever paused. This is reset
+ // when reorder becomes allowed.
+ mAvalancheController.setEnableAtRuntime(false);
+
+ // Note that we cannot do the above when
+ // 1) The remove runnable runs because its delay means it may not run before shade close
+ // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
+ // will have started by then
+ }
+ };
+
///////////////////////////////////////////////////////////////////////////////////////////////
// HeadsUpManager utility (protected) methods overrides:
@@ -561,18 +591,36 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
}
@Override
- protected Runnable createRemoveRunnable(NotificationEntry entry) {
- return () -> {
+ protected void setEntry(@androidx.annotation.NonNull NotificationEntry entry,
+ @androidx.annotation.Nullable Runnable removeRunnable) {
+ super.setEntry(entry, removeRunnable);
+
+ if (NotificationThrottleHun.isEnabled()) {
if (!mVisualStabilityProvider.isReorderingAllowed()
// We don't want to allow reordering while pulsing, but headsup need to
// time out anyway
&& !entry.showingPulsing()) {
mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ entry.setSeenInShade(true);
+ }
+ }
+ }
+
+ @Override
+ protected Runnable createRemoveRunnable(NotificationEntry entry) {
+ return () -> {
+ if (!NotificationThrottleHun.isEnabled()
+ && !mVisualStabilityProvider.isReorderingAllowed()
+ // We don't want to allow reordering while pulsing, but headsup need to
+ // time out anyway
+ && !entry.showingPulsing()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
mOnReorderingAllowedListener);
} else if (mTrackingHeadsUp) {
mEntriesToRemoveAfterExpand.add(entry);
- } else {
+ } else if (mVisualStabilityProvider.isReorderingAllowed()
+ || entry.showingPulsing()) {
removeEntry(entry.getKey(), "createRemoveRunnable");
}
};
@@ -585,8 +633,10 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
mEntriesToRemoveAfterExpand.remove(mEntry);
}
- if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+ if (!NotificationThrottleHun.isEnabled()) {
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index e69a78fb14fd..1a4708170a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -40,7 +40,6 @@ import com.android.systemui.assist.AssistManager
import com.android.systemui.camera.CameraIntents
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.dagger.qualifiers.Main
@@ -208,10 +207,16 @@ constructor(
val cancelRunnable = Runnable {
callback?.onActivityStarted(ActivityManager.START_CANCELED)
}
- // Do not deferKeyguard when occluded because, when keyguard is occluded,
- // we do not launch the activity until keyguard is done.
+ // Do not deferKeyguard when occluded because, when keyguard is occluded, we do not launch
+ // the activity until keyguard is done. The only exception is when we're on the Hub and want
+ // to dismiss the shade immediately, which means that another animation will take care of
+ // the transition.
val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded)
- val deferred = !occluded
+ val dismissOnCommunal =
+ communalSettingsInteractor.isCommunalFlagEnabled() &&
+ communalSceneInteractor.isCommunalVisible.value &&
+ dismissShadeDirectly
+ val deferred = !occluded || dismissOnCommunal
executeRunnableDismissingKeyguard(
runnable,
cancelRunnable,
@@ -463,10 +468,18 @@ constructor(
object : ActivityStarter.OnDismissAction {
override fun onDismiss(): Boolean {
if (runnable != null) {
+ // We don't wait for Keyguard to be gone if we're dismissing the shade
+ // immediately and we're on the Communal Hub. This is to make sure that the
+ // Hub -> Edit Mode transition is seamless.
+ val dismissOnCommunal =
+ communalSettingsInteractor.isCommunalFlagEnabled() &&
+ communalSceneInteractor.isCommunalVisible.value &&
+ dismissShade
if (
keyguardStateController.isShowing &&
keyguardStateController.isOccluded &&
- !isCommunalWidgetLaunch()
+ !isCommunalWidgetLaunch() &&
+ !dismissOnCommunal
) {
statusBarKeyguardViewManagerLazy
.get()
@@ -562,12 +575,6 @@ constructor(
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationStart(isExpandingFullyAbove)
- if (communalSettingsInteractor.isCommunalFlagEnabled()) {
- communalSceneInteractor.snapToScene(
- CommunalScenes.Blank,
- ActivityTransitionAnimator.TIMINGS.totalDuration
- )
- }
// Double check that the keyguard is still showing and not going
// away, but if so set the keyguard occluded. Typically, WM will let
// KeyguardViewMediator know directly, but we're overriding that to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index 4c97854bb5c9..16bd7f830c66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -110,7 +110,7 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
chipView.setOnClickListener(chipModel.onClickListener)
// Accessibility
- setChipAccessibility(chipModel, chipView)
+ setChipAccessibility(chipModel, chipView, chipBackgroundView)
// Colors
val textColor = chipModel.colors.text(chipContext)
@@ -213,7 +213,11 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
this.setPaddingRelative(/* start= */ 0, paddingTop, paddingEnd, paddingBottom)
}
- private fun setChipAccessibility(chipModel: OngoingActivityChipModel.Shown, chipView: View) {
+ private fun setChipAccessibility(
+ chipModel: OngoingActivityChipModel.Shown,
+ chipView: View,
+ chipBackgroundView: View,
+ ) {
when (chipModel) {
is OngoingActivityChipModel.Shown.Countdown -> {
// Set as assertive so talkback will announce the countdown
@@ -224,6 +228,16 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
}
}
+ // Clickable chips need to be a minimum size for accessibility purposes, but let
+ // non-clickable chips be smaller.
+ if (chipModel.onClickListener != null) {
+ chipBackgroundView.minimumWidth =
+ chipBackgroundView.context.resources.getDimensionPixelSize(
+ R.dimen.min_clickable_item_size
+ )
+ } else {
+ chipBackgroundView.minimumWidth = 0
+ }
}
private fun animateLightsOutView(view: View, visible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 40799583a7b9..a88c6d707da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -37,13 +37,27 @@ import javax.inject.Inject
@SysUISingleton
class AvalancheController
@Inject
-constructor(dumpManager: DumpManager,
- private val uiEventLogger: UiEventLogger,
- @Background private val bgHandler: Handler
+constructor(
+ dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger,
+ @Background private val bgHandler: Handler
) : Dumpable {
private val tag = "AvalancheController"
private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
+ var enableAtRuntime = true
+ set(value) {
+ if (!value) {
+ // Waiting HUNs in AvalancheController are shown in the HUN section in open shade.
+ // Clear them so we don't show them again when the shade closes and reordering is
+ // allowed again.
+ logDroppedHunsInBackground(getWaitingKeys().size)
+ clearNext()
+ }
+ if (field != value) {
+ field = value
+ }
+ }
// HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
@VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -90,13 +104,17 @@ constructor(dumpManager: DumpManager,
return getKey(headsUpEntryShowing)
}
+ fun isEnabled(): Boolean {
+ return NotificationThrottleHun.isEnabled && enableAtRuntime
+ }
+
/** Run or delay Runnable for given HeadsUpEntry */
fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
if (runnable == null) {
log { "Runnable is NULL, stop update." }
return
}
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
runnable.run()
return
}
@@ -156,7 +174,7 @@ constructor(dumpManager: DumpManager,
log { "Runnable is NULL, stop delete." }
return
}
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
runnable.run()
return
}
@@ -185,7 +203,8 @@ constructor(dumpManager: DumpManager,
showNext()
runnable.run()
} else {
- log { "$fn => removing untracked ${getKey(entry)}" }
+ log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+ runnable.run()
}
logState("after $fn")
}
@@ -197,7 +216,7 @@ constructor(dumpManager: DumpManager,
* BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
*/
fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
}
@@ -246,7 +265,7 @@ constructor(dumpManager: DumpManager,
/** Return true if entry is waiting to show. */
fun isWaiting(key: String): Boolean {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return false
}
for (entry in nextMap.keys) {
@@ -259,7 +278,7 @@ constructor(dumpManager: DumpManager,
/** Return list of keys for huns waiting */
fun getWaitingKeys(): MutableList<String> {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return mutableListOf()
}
val keyList = mutableListOf<String>()
@@ -270,7 +289,7 @@ constructor(dumpManager: DumpManager,
}
fun getWaitingEntry(key: String): HeadsUpEntry? {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return null
}
for (headsUpEntry in nextMap.keys) {
@@ -282,7 +301,7 @@ constructor(dumpManager: DumpManager,
}
fun getWaitingEntryList(): List<HeadsUpEntry> {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return mutableListOf()
}
return nextMap.keys.toList()
@@ -340,13 +359,15 @@ constructor(dumpManager: DumpManager,
showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
}
- fun logDroppedHunsInBackground(numDropped: Int) {
- bgHandler.post(Runnable {
- // Do this in the background to avoid missing frames when closing the shade
- for (n in 1..numDropped) {
- uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+ private fun logDroppedHunsInBackground(numDropped: Int) {
+ bgHandler.post(
+ Runnable {
+ // Do this in the background to avoid missing frames when closing the shade
+ for (n in 1..numDropped) {
+ uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+ }
}
- })
+ )
}
fun clearNext() {
@@ -367,7 +388,8 @@ constructor(dumpManager: DumpManager,
"\nPREVIOUS: [$previousHunKey]" +
"\nNEXT LIST: $nextListStr" +
"\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr"
+ "\nDROPPED: $dropSetStr" +
+ "\nENABLED: $enableAtRuntime"
}
private fun logState(reason: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 220e729625af..65171358b050 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
+import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.GlobalSettings;
@@ -726,6 +727,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* of AvalancheController that take it as param.
*/
public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public boolean mRemoteInputActivatedAtLeastOnce;
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -756,7 +758,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
setEntry(entry, createRemoveRunnable(entry));
}
- private void setEntry(@NonNull final NotificationEntry entry,
+ protected void setEntry(@NonNull final NotificationEntry entry,
@Nullable Runnable removeRunnable) {
mEntry = entry;
mRemoveRunnable = removeRunnable;
@@ -835,6 +837,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
public boolean isSticky() {
if (mEntry == null) return false;
+
+ if (ExpandHeadsUpOnInlineReply.isEnabled()) {
+ // we don't consider pinned and expanded huns as sticky after the remote input
+ // has been activated for them
+ if (!mRemoteInputActive && mRemoteInputActivatedAtLeastOnce) {
+ return false;
+ }
+ }
+
return (mEntry.isRowPinned() && mExpanded)
|| mRemoteInputActive
|| hasFullScreenIntent(mEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
index 483855409b91..07bbca74e12e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
@@ -31,6 +31,13 @@ interface DeviceProvisioningRepository {
* @see android.provider.Settings.Global.DEVICE_PROVISIONED
*/
val isDeviceProvisioned: Flow<Boolean>
+
+ /**
+ * Whether this device has been provisioned.
+ *
+ * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+ */
+ fun isDeviceProvisioned(): Boolean
}
@Module
@@ -48,11 +55,15 @@ constructor(
val listener =
object : DeviceProvisionedController.DeviceProvisionedListener {
override fun onDeviceProvisionedChanged() {
- trySend(deviceProvisionedController.isDeviceProvisioned)
+ trySend(isDeviceProvisioned())
}
}
deviceProvisionedController.addCallback(listener)
- trySend(deviceProvisionedController.isDeviceProvisioned)
+ trySend(isDeviceProvisioned())
awaitClose { deviceProvisionedController.removeCallback(listener) }
}
+
+ override fun isDeviceProvisioned(): Boolean {
+ return deviceProvisionedController.isDeviceProvisioned
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
index 66ed092fcce1..ace4ce0673a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.Flow
class DeviceProvisioningInteractor
@Inject
constructor(
- repository: DeviceProvisioningRepository,
+ private val repository: DeviceProvisioningRepository,
) {
/**
* Whether this device has been provisioned.
@@ -34,4 +34,8 @@ constructor(
* @see android.provider.Settings.Global.DEVICE_PROVISIONED
*/
val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+ fun isDeviceProvisioned(): Boolean {
+ return repository.isDeviceProvisioned()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index c45f98e5f4f5..066bfc5c588d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -18,6 +18,8 @@ package com.android.systemui.volume;
import static android.media.AudioManager.RINGER_MODE_NORMAL;
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
+
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -59,6 +61,8 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
@@ -76,6 +80,8 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
import dalvik.annotation.optimization.NeverCompile;
@@ -102,7 +108,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
- private static final int DYNAMIC_STREAM_START_INDEX = 100;
+ // We only need one dynamic stream for broadcast because at most two headsets are allowed
+ // to join local broadcast in current stage.
+ // It is safe to use 99 as the broadcast stream now. There are only 10+ default audio
+ // streams defined in AudioSystem for now and audio team is in the middle of restructure,
+ // no new default stream is preferred.
+ @VisibleForTesting static final int DYNAMIC_STREAM_BROADCAST = 99;
+ private static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -145,6 +157,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final State mState = new State();
protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
private final VibratorHelper mVibrator;
+ private final AudioSharingInteractor mAudioSharingInteractor;
+ private final JavaAdapter mJavaAdapter;
private final boolean mHasVibrator;
private boolean mShowA11yStream;
private boolean mShowVolumeDialog;
@@ -188,7 +202,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ AudioSharingInteractor audioSharingInteractor,
+ JavaAdapter javaAdapter
) {
mContext = context.getApplicationContext();
mPackageManager = packageManager;
@@ -200,6 +216,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
+ mAudioSharingInteractor = audioSharingInteractor;
+ mJavaAdapter = javaAdapter;
mAudio = audioManager;
mNoMan = notificationManager;
mObserver = new SettingObserver(mWorker);
@@ -272,6 +290,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
} catch (SecurityException e) {
Log.w(TAG, "No access to media sessions", e);
}
+ if (volumeDialogAudioSharingFix()) {
+ Slog.d(TAG, "Start collect volume changes in audio sharing");
+ mJavaAdapter.alwaysCollectFlow(
+ mAudioSharingInteractor.getVolume(),
+ this::handleAudioSharingStreamVolumeChanges);
+ }
}
public void setVolumePolicy(VolumePolicy policy) {
@@ -545,7 +569,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mState.activeStream = activeStream;
Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
- final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
+ final int s =
+ activeStream
+ < (volumeDialogAudioSharingFix()
+ ? DYNAMIC_STREAM_BROADCAST
+ : DYNAMIC_STREAM_REMOTE_START_INDEX)
+ ? activeStream
+ : -1;
if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
mAudio.forceVolumeControlStream(s);
return true;
@@ -726,7 +756,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private void onSetStreamVolumeW(int stream, int level) {
if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
- if (stream >= DYNAMIC_STREAM_START_INDEX) {
+ if (volumeDialogAudioSharingFix() && stream == DYNAMIC_STREAM_BROADCAST) {
+ Slog.d(TAG, "onSetStreamVolumeW set broadcast stream level = " + level);
+ mAudioSharingInteractor.setStreamVolume(level);
+ return;
+ }
+ if (stream >= DYNAMIC_STREAM_REMOTE_START_INDEX) {
mMediaSessionsCallbacksW.setStreamVolume(stream, level);
return;
}
@@ -758,6 +793,40 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
DndTile.setVisible(mContext, true);
}
+ void handleAudioSharingStreamVolumeChanges(@Nullable Integer volume) {
+ if (volume == null) {
+ if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
+ mState.states.remove(DYNAMIC_STREAM_BROADCAST);
+ Slog.d(TAG, "Remove audio sharing stream");
+ mCallbacks.onStateChanged(mState);
+ }
+ } else {
+ if (mState.states.contains(DYNAMIC_STREAM_BROADCAST)) {
+ StreamState ss = mState.states.get(DYNAMIC_STREAM_BROADCAST);
+ if (ss.level != volume) {
+ ss.level = volume;
+ Slog.d(TAG, "updateState, audio sharing stream volume = " + volume);
+ mCallbacks.onStateChanged(mState);
+ }
+ } else {
+ StreamState ss = streamStateW(DYNAMIC_STREAM_BROADCAST);
+ ss.dynamic = true;
+ ss.levelMin = mAudioSharingInteractor.getVolumeMin();
+ ss.levelMax = mAudioSharingInteractor.getVolumeMax();
+ if (ss.level != volume) {
+ ss.level = volume;
+ }
+ String label = mContext.getString(R.string.audio_sharing_description);
+ if (!Objects.equals(ss.remoteLabel, label)) {
+ ss.name = -1;
+ ss.remoteLabel = label;
+ }
+ Slog.d(TAG, "updateState, new audio sharing stream volume = " + volume);
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
private final class VC extends IVolumeController.Stub {
private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";
@@ -1256,7 +1325,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
- private int mNextStream = DYNAMIC_STREAM_START_INDEX;
+ private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
public MediaSessionsCallbacks(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 6b02e1ada491..0770d8926389 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
import static com.android.systemui.Flags.hapticVolumeSlider;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -1678,6 +1679,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
return true;
}
+ // Always show the stream for audio sharing if it exists.
+ if (volumeDialogAudioSharingFix()
+ && row.ss != null
+ && mContext.getString(R.string.audio_sharing_description)
+ .equals(row.ss.remoteLabel)) {
+ return true;
+ }
+
if (row.defaultStream) {
return activeRow.stream == STREAM_RING
|| activeRow.stream == STREAM_ALARM
@@ -1880,10 +1889,25 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (!ss.dynamic) continue;
mDynamic.put(stream, true);
if (findRow(stream) == null) {
- addRow(stream,
- com.android.settingslib.R.drawable.ic_volume_remote,
- com.android.settingslib.R.drawable.ic_volume_remote_mute,
- true, false, true);
+ if (volumeDialogAudioSharingFix()
+ && mContext.getString(R.string.audio_sharing_description)
+ .equals(ss.remoteLabel)) {
+ addRow(
+ stream,
+ R.drawable.ic_volume_media,
+ R.drawable.ic_volume_media_mute,
+ true,
+ false,
+ true);
+ } else {
+ addRow(
+ stream,
+ com.android.settingslib.R.drawable.ic_volume_remote,
+ com.android.settingslib.R.drawable.ic_volume_remote_mute,
+ true,
+ false,
+ true);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
index 29040923a2cc..cf80263b4d14 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
@@ -16,20 +16,15 @@
package com.android.systemui.volume.dagger
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
+import dagger.Binds
import dagger.Module
-import dagger.Provides
/** Dagger module for empty audio sharing impl for unnecessary volume overlay */
@Module
interface AudioSharingEmptyImplModule {
- companion object {
- @Provides
- @SysUISingleton
- fun provideAudioSharingInteractor(): AudioSharingInteractor =
- AudioSharingInteractorEmptyImpl()
- }
+ @Binds
+ fun bindsAudioSharingInteractor(impl: AudioSharingInteractorEmptyImpl): AudioSharingInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index 4d29788edf68..aba3015a6b7d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -77,7 +77,7 @@ constructor(
}
@SysUISingleton
-class AudioSharingInteractorEmptyImpl : AudioSharingInteractor {
+class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
override val volume: Flow<Int?> = emptyFlow()
override val volumeMin: Int = EMPTY_VOLUME
override val volumeMax: Int = EMPTY_VOLUME
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec9b5cfbdeb2..14cd202b7ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,8 +20,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -66,6 +67,7 @@ import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
@@ -249,7 +251,25 @@ public final class WMShell implements
pip.showPictureInPictureMenu();
}
});
+ pip.registerPipTransitionCallback(
+ new PipTransitionController.PipTransitionCallback() {
+ @Override
+ public void onPipTransitionStarted(int direction, Rect pipBounds) {
+ mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+
+ @Override
+ public void onPipTransitionFinished(int direction) {
+ mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+ @Override
+ public void onPipTransitionCanceled(int direction) {
+ // No op.
+ }
+ }, mSysUiMainExecutor);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
@@ -263,6 +283,11 @@ public final class WMShell implements
public void onFinishedWakingUp() {
splitScreen.onFinishedWakingUp();
}
+
+ @Override
+ public void onStartedGoingToSleep() {
+ splitScreen.onStartedGoingToSleep();
+ }
});
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 41a41164b2a5..038b81b34d77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -216,6 +216,16 @@ public class MagnificationTest extends SysuiTestCase {
}
@Test
+ public void onRestoreWindowSize_updateSettingsButtonStatusOnRestore() {
+ mMagnification.mWindowMagnifierCallback
+ .onWindowMagnifierBoundsRestored(TEST_DISPLAY, MagnificationSize.SMALL);
+ waitForIdleSync();
+
+ verify(mMagnificationSettingsController)
+ .updateSettingsButtonStatusOnRestore(MagnificationSize.SMALL);
+ }
+
+ @Test
public void onSetMagnifierSize_delegateToMagnifier() {
final @MagnificationSize int index = MagnificationSize.SMALL;
mMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index e27268292763..e01366a9e594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -67,9 +67,8 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -130,14 +129,12 @@ import java.util.function.Supplier;
@LargeTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4.class)
-@RequiresFlagsEnabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
+@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
@Rule
// NOTE: pass 'null' to allow this test advances time on the main thread.
public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
@Mock
@@ -625,6 +622,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
0);
}
+ @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
int newSmallestScreenWidthDp =
@@ -663,6 +661,50 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
}
+ @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+ @Test
+ public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
+ int newSmallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+ int windowFrameSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
+ mSharedPreferences
+ .edit()
+ .putString(String.valueOf(newSmallestScreenWidthDp),
+ WindowMagnificationFrameSpec.serialize(
+ WindowMagnificationSettings.MagnificationSize.CUSTOM,
+ preferredWindowSize))
+ .commit();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ // Screen density and size change
+ mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mWindowManager.setWindowBounds(testWindowBounds);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+ });
+
+ // wait for rect update
+ waitForIdleSync();
+ verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
+ eq(mContext.getDisplayId()),
+ eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
+ ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // The width and height of the view include the magnification frame and the margins.
+ assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin));
+ assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
+ }
+
@Test
public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
mInstrumentation.runOnMainSync(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index ad9053a7edda..944066fa4954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility;
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -23,12 +25,15 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.util.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
@@ -54,19 +59,59 @@ public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase {
mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
}
+ @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_getExpectedSize() {
Size testSize = new Size(500, 500);
- mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize);
+ mWindowMagnificationFrameSizePrefs
+ .saveIndexAndSizeForCurrentDensity(MagnificationSize.CUSTOM, testSize);
assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
.isEqualTo(testSize);
}
+ @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+ @Test
+ public void saveSizeForCurrentDensity_validPreference_getExpectedSize() {
+ int testIndex = MagnificationSize.MEDIUM;
+ Size testSize = new Size(500, 500);
+ mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize);
+
+ assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
+ .isEqualTo(testSize);
+ }
+
+ @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+ @Test
+ public void saveSizeForCurrentDensity_validPreference_getExpectedIndex() {
+ int testIndex = MagnificationSize.MEDIUM;
+ Size testSize = new Size(500, 500);
+ mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize);
+
+ assertThat(mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity())
+ .isEqualTo(testIndex);
+ }
+
+ @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+ @Test
+ public void saveSizeForCurrentDensity_invalidPreference_getDefaultIndex() {
+ mSharedPreferences
+ .edit()
+ .putString(
+ String.valueOf(
+ mContext.getResources().getConfiguration().smallestScreenWidthDp),
+ "100x200")
+ .commit();
+
+ assertThat(mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity())
+ .isEqualTo(MagnificationSize.DEFAULT);
+ }
+
@Test
public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() {
+ int testIndex = MagnificationSize.MEDIUM;
Size testSize = new Size(500, 500);
- mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize);
+ mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize);
assertThat(mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity())
.isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
new file mode 100644
index 000000000000..791a26ed761d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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.systemui.accessibility
+
+import android.testing.AndroidTestingRunner
+import android.util.Size
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WindowMagnificationFrameSpecTest : SysuiTestCase() {
+
+ @Test
+ fun deserializeSpec_validSpec_expectedIndex() {
+ val targetIndex = MagnificationSize.LARGE
+ val targetSize = Size(100, 200)
+ val targetPreference = WindowMagnificationFrameSpec.serialize(targetIndex, targetSize)
+
+ assertThat(WindowMagnificationFrameSpec.deserialize(targetPreference).index)
+ .isEqualTo(targetIndex)
+ }
+
+ @Test
+ fun deserializeSpec_validSpec_expectedSize() {
+ val targetIndex = MagnificationSize.LARGE
+ val targetSize = Size(100, 200)
+ val targetPreference = WindowMagnificationFrameSpec.serialize(targetIndex, targetSize)
+
+ assertThat(WindowMagnificationFrameSpec.deserialize(targetPreference).size)
+ .isEqualTo(targetSize)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 639b53bfb95b..5600b87280ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -710,6 +710,35 @@ public class TouchMonitorTest extends SysuiTestCase {
environment.verifyLifecycleObserversUnregistered();
}
+ @Test
+ public void testLastSessionPop_createsNewInputSession() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final TouchHandler.TouchSession.Callback callback =
+ Mockito.mock(TouchHandler.TouchSession.Callback.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
+ session.registerCallback(callback);
+
+ // Clear invocations on input session and factory.
+ clearInvocations(environment.mInputFactory);
+ clearInvocations(environment.mInputSession);
+
+ // Pop only active touch session.
+ session.pop();
+ environment.executeAll();
+
+ // Verify that input session disposed and new session requested from factory.
+ verify(environment.mInputSession).dispose();
+ verify(environment.mInputFactory).create(any(), any(), any(), anyBoolean());
+ }
+
private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index d940fc938c4b..7a4bbfe9a580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -36,7 +36,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -77,9 +76,6 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
-
- mContext.getOrCreateTestableResources().addOverride(
- R.bool.flag_battery_shield_icon, false);
}
@Test
@@ -136,26 +132,6 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
verify(mTunerService, never()).addTunable(any(), any());
}
- @Test
- public void shieldFlagDisabled_viewNotified() {
- mContext.getOrCreateTestableResources().addOverride(
- R.bool.flag_battery_shield_icon, false);
-
- initController();
-
- verify(mBatteryMeterView).setDisplayShieldEnabled(false);
- }
-
- @Test
- public void shieldFlagEnabled_viewNotified() {
- mContext.getOrCreateTestableResources().addOverride(
- R.bool.flag_battery_shield_icon, true);
-
- initController();
-
- verify(mBatteryMeterView).setDisplayShieldEnabled(true);
- }
-
private void initController() {
mController = new BatteryMeterViewController(
mBatteryMeterView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 2bd0976f30dc..2aa33a176ac9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -22,9 +22,9 @@ import android.widget.ImageView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.flags.Flags.FLAG_NEW_STATUS_BAR_ICONS
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -67,9 +67,8 @@ class BatteryMeterViewTest : SysuiTestCase() {
fun contentDescription_unknown() {
mBatteryMeterView.onBatteryUnknownStateChanged(true)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_unknown)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_unknown))
}
@Test
@@ -80,11 +79,10 @@ class BatteryMeterViewTest : SysuiTestCase() {
mBatteryMeterView.updatePercentText()
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(
- R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
- )
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(
+ context.getString(R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE)
+ )
}
@Test
@@ -96,13 +94,14 @@ class BatteryMeterViewTest : SysuiTestCase() {
mBatteryMeterView.updatePercentText()
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(
context.getString(
- R.string.accessibility_battery_level_charging_paused_with_estimate,
- 17,
- ESTIMATE,
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 17,
+ ESTIMATE,
)
- )
+ )
}
@Test
@@ -110,27 +109,24 @@ class BatteryMeterViewTest : SysuiTestCase() {
mBatteryMeterView.onBatteryLevelChanged(90, false)
mBatteryMeterView.onIsBatteryDefenderChanged(true)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level_charging_paused, 90)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level_charging_paused, 90))
}
@Test
fun contentDescription_charging() {
mBatteryMeterView.onBatteryLevelChanged(45, true)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level_charging, 45)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level_charging, 45))
}
@Test
fun contentDescription_notCharging() {
mBatteryMeterView.onBatteryLevelChanged(45, false)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level, 45)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level, 45))
}
@Test
@@ -138,9 +134,8 @@ class BatteryMeterViewTest : SysuiTestCase() {
mBatteryMeterView.onBatteryLevelChanged(45, true)
mBatteryMeterView.onIsIncompatibleChargingChanged(true)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level, 45)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level, 45))
}
@Test
@@ -152,19 +147,17 @@ class BatteryMeterViewTest : SysuiTestCase() {
mBatteryMeterView.updatePercentText()
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(
- R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
- )
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(
+ context.getString(R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE)
+ )
// Update the show mode from estimate to percent
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level, 15)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level, 15))
}
@Test
@@ -176,19 +169,17 @@ class BatteryMeterViewTest : SysuiTestCase() {
mBatteryMeterView.updatePercentText()
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(
- R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(
+ context.getString(R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE)
)
- )
// Update the show mode from estimate to percent
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
assertThat(mBatteryMeterView.batteryPercentView).isNull()
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level, 15)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level, 15))
assertThat(mBatteryMeterView.unifiedBatteryState.showPercent).isTrue()
}
@@ -225,49 +216,47 @@ class BatteryMeterViewTest : SysuiTestCase() {
// BatteryDefender
mBatteryMeterView.onBatteryLevelChanged(90, false)
mBatteryMeterView.onIsBatteryDefenderChanged(true)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level_charging_paused, 90)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level_charging_paused, 90))
// BatteryDefender & estimate
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
mBatteryMeterView.updatePercentText()
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(
context.getString(
- R.string.accessibility_battery_level_charging_paused_with_estimate,
- 90,
- ESTIMATE,
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 90,
+ ESTIMATE,
)
- )
+ )
// Just estimate
mBatteryMeterView.onIsBatteryDefenderChanged(false)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(
context.getString(
- R.string.accessibility_battery_level_with_estimate,
- 90,
- ESTIMATE,
+ R.string.accessibility_battery_level_with_estimate,
+ 90,
+ ESTIMATE,
)
- )
+ )
// Just percent
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level, 90)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level, 90))
// Charging
mBatteryMeterView.onBatteryLevelChanged(90, true)
- assertThat(mBatteryMeterView.contentDescription).isEqualTo(
- context.getString(R.string.accessibility_battery_level_charging, 90)
- )
+ assertThat(mBatteryMeterView.contentDescription)
+ .isEqualTo(context.getString(R.string.accessibility_battery_level_charging, 90))
}
@Test
@DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOff() {
- mBatteryMeterView.setDisplayShieldEnabled(true)
val drawable = getBatteryDrawable()
mBatteryMeterView.onIsBatteryDefenderChanged(true)
@@ -278,8 +267,6 @@ class BatteryMeterViewTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOn() {
- mBatteryMeterView.setDisplayShieldEnabled(true)
-
mBatteryMeterView.onIsBatteryDefenderChanged(true)
assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull()
@@ -288,7 +275,6 @@ class BatteryMeterViewTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOff() {
- mBatteryMeterView.setDisplayShieldEnabled(true)
val drawable = getBatteryDrawable()
// Start as true
@@ -303,8 +289,6 @@ class BatteryMeterViewTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOn() {
- mBatteryMeterView.setDisplayShieldEnabled(true)
-
// Start as true
mBatteryMeterView.onIsBatteryDefenderChanged(true)
@@ -316,27 +300,6 @@ class BatteryMeterViewTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
- fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOff() {
- mBatteryMeterView.setDisplayShieldEnabled(false)
- val drawable = getBatteryDrawable()
-
- mBatteryMeterView.onIsBatteryDefenderChanged(true)
-
- assertThat(drawable.displayShield).isFalse()
- }
-
- @Test
- @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
- fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOn() {
- mBatteryMeterView.setDisplayShieldEnabled(false)
-
- mBatteryMeterView.onIsBatteryDefenderChanged(true)
-
- assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
- }
-
- @Test
- @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOff() {
mBatteryMeterView.onBatteryLevelChanged(45, true)
val drawable = getBatteryDrawable()
@@ -381,8 +344,8 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
- return (mBatteryMeterView.getChildAt(0) as ImageView)
- .drawable as AccessorizedBatteryDrawable
+ return (mBatteryMeterView.getChildAt(0) as ImageView).drawable
+ as AccessorizedBatteryDrawable
}
private class Fetcher : BatteryEstimateFetcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 677d1fdaa01d..6dcea144f2a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -449,150 +449,214 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun shows_authenticated_no_errors_no_confirmation_required() = runGenericTest {
- if (!testCase.confirmationRequested) {
- val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
- val iconContentDescriptionId by
- collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
- val shouldAnimateIconView by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
- verifyIconSize()
+ fun shows_error_to_unlock_or_success() {
+ // Face-only auth does not use error -> unlock or error -> success assets
+ if (testCase.isFingerprintOnly || testCase.isCoex) {
+ runGenericTest {
+ // Distinct asset for error -> success only applicable for fingerprint-only /
+ // explicit co-ex auth
+ val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ val iconContentDescriptionId by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+
+ var forceExplicitFlow =
+ testCase.isCoex && testCase.confirmationRequested ||
+ testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ kosmos.promptViewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+ verifyIconSize(forceExplicitFlow)
- kosmos.promptViewModel.showAuthenticated(
- modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
- )
+ kosmos.promptViewModel.ensureFingerprintHasStarted(isDelayed = true)
+ kosmos.promptViewModel.iconViewModel.setPreviousIconWasError(true)
- if (testCase.isFingerprintOnly) {
- // Fingerprint icon asset assertions
- if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
- assertThat(iconOverlayAsset)
- .isEqualTo(R.raw.biometricprompt_symbol_fingerprint_to_success_landscape)
- assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
- assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(true)
- } else {
- assertThat(iconAsset)
- .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+ kosmos.promptViewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ // TODO(b/350121748): SFPS test cases to be added after SFPS assets update
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ // Non-SFPS (UDFPS / rear-FPS) test cases
+ // Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint
+ if (testCase.authenticatedByFingerprint) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_success_lottie)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ } else { // co-ex, authenticated by face
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_unlock_lottie)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+
+ // Confirm authentication
+ kosmos.promptViewModel.confirmAuthenticated()
+
+ assertThat(iconAsset)
+ .isEqualTo(
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ )
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_no_errors_no_confirmation_required() {
+ if (!testCase.confirmationRequested) {
+ runGenericTest {
+ val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ val iconOverlayAsset by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
+ val iconContentDescriptionId by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+ val shouldAnimateIconOverlay by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
+ verifyIconSize()
+
+ kosmos.promptViewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFingerprintOnly) {
+ // Fingerprint icon asset assertions
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
+ assertThat(iconOverlayAsset)
+ .isEqualTo(
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ )
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ } else if (testCase.isFaceOnly || testCase.isCoex) {
+ // Face icon asset assertions
+ // If co-ex, use implicit flow (explicit flow always requires confirmation)
+ assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
- } else if (testCase.isFaceOnly || testCase.isCoex) {
- // Face icon asset assertions
- // If co-ex, use implicit flow (explicit flow always requires confirmation)
- assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
- assertThat(iconOverlayAsset).isEqualTo(-1)
- assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
- assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
}
}
@Test
- fun shows_pending_confirmation() = runGenericTest {
- if (
- (testCase.isFaceOnly || testCase.isCoex) &&
- testCase.authenticatedByFace &&
- testCase.confirmationRequested
- ) {
- val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
- val iconContentDescriptionId by
- collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
- val shouldAnimateIconView by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
-
- val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
- verifyIconSize(forceExplicitFlow = forceExplicitFlow)
-
- kosmos.promptViewModel.showAuthenticated(
- modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
- )
+ fun shows_pending_confirmation() {
+ if (testCase.authenticatedByFace && testCase.confirmationRequested) {
+ runGenericTest {
+ val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ val iconOverlayAsset by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
+ val iconContentDescriptionId by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+ val shouldAnimateIconOverlay by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
+ verifyIconSize(forceExplicitFlow = forceExplicitFlow)
+
+ kosmos.promptViewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
- if (testCase.isFaceOnly) {
- assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark)
- assertThat(iconOverlayAsset).isEqualTo(-1)
- assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
- assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
- } else if (testCase.isCoex) { // explicit flow, confirmation requested
- // TODO: Update when SFPS co-ex is implemented
- if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset)
- .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+ if (testCase.isFaceOnly) {
+ assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark)
assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ } else if (testCase.isCoex) { // explicit flow, confirmation requested
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
}
}
}
}
@Test
- fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
- if (
- (testCase.isFaceOnly || testCase.isCoex) &&
- testCase.authenticatedByFace &&
- testCase.confirmationRequested
- ) {
- val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
- val iconContentDescriptionId by
- collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
- val shouldAnimateIconView by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
- val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
- verifyIconSize(forceExplicitFlow = forceExplicitFlow)
-
- kosmos.promptViewModel.showAuthenticated(
- modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
- )
-
- kosmos.promptViewModel.confirmAuthenticated()
+ fun shows_authenticated_explicitly_confirmed() {
+ if (testCase.authenticatedByFace && testCase.confirmationRequested) {
+ runGenericTest {
+ val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ val iconOverlayAsset by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
+ val iconContentDescriptionId by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+ val shouldAnimateIconOverlay by
+ collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
+ val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
+ verifyIconSize(forceExplicitFlow = forceExplicitFlow)
+
+ kosmos.promptViewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
- if (testCase.isFaceOnly) {
- assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
- assertThat(iconOverlayAsset).isEqualTo(-1)
- assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
- assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
- }
+ kosmos.promptViewModel.confirmAuthenticated()
- // explicit flow because confirmation requested
- if (testCase.isCoex) {
- // TODO: Update when SFPS co-ex is implemented
- if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset)
- .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+ if (testCase.isFaceOnly) {
+ assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
- .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
assertThat(shouldAnimateIconView).isEqualTo(true)
assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ )
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
}
}
}
@@ -700,58 +764,68 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun sfpsIconUpdates_onFoldConfigurationChanged() = runGenericTest {
+ fun sfpsIconUpdates_onFoldConfigurationChanged() {
if (
testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON &&
!testCase.isInRearDisplayMode
) {
- val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ runGenericTest {
+ val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- kosmos.promptViewModel.iconViewModel.onConfigurationChanged(getFoldedConfiguration())
- val foldedIcon = currentIcon
+ kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
+ getFoldedConfiguration()
+ )
+ val foldedIcon = currentIcon
- kosmos.promptViewModel.iconViewModel.onConfigurationChanged(getUnfoldedConfiguration())
- val unfoldedIcon = currentIcon
+ kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
+ getUnfoldedConfiguration()
+ )
+ val unfoldedIcon = currentIcon
- assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+ assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+ }
}
}
@Test
- fun sfpsIconUpdates_onRotation() = runGenericTest {
+ fun sfpsIconUpdates_onRotation() {
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ runGenericTest {
+ val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
- val iconRotation0 = currentIcon
+ kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val iconRotation0 = currentIcon
- kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
- val iconRotation90 = currentIcon
+ kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ val iconRotation90 = currentIcon
- kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
- val iconRotation180 = currentIcon
+ kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val iconRotation180 = currentIcon
- kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
- val iconRotation270 = currentIcon
+ kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ val iconRotation270 = currentIcon
- assertThat(iconRotation0).isEqualTo(iconRotation180)
- assertThat(iconRotation0).isNotEqualTo(iconRotation90)
- assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+ assertThat(iconRotation0).isEqualTo(iconRotation180)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+ }
}
}
@Test
- fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+ fun sfpsIconUpdates_onRearDisplayMode() {
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ runGenericTest {
+ val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- kosmos.displayStateRepository.setIsInRearDisplayMode(false)
- val iconNotRearDisplayMode = currentIcon
+ kosmos.displayStateRepository.setIsInRearDisplayMode(false)
+ val iconNotRearDisplayMode = currentIcon
- kosmos.displayStateRepository.setIsInRearDisplayMode(true)
- val iconRearDisplayMode = currentIcon
+ kosmos.displayStateRepository.setIsInRearDisplayMode(true)
+ val iconRearDisplayMode = currentIcon
- assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+ assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
new file mode 100644
index 000000000000..4d112e93013a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.source
+
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager
+import android.view.mockWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CurrentAppShortcutsSourceTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mockWindowManager = kosmos.mockWindowManager
+ private val source = CurrentAppShortcutsSource(mockWindowManager)
+
+ private var shortcutGroups: List<KeyboardShortcutGroup>? = null
+
+ @Before
+ fun setUp() {
+ whenever(mockWindowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer {
+ val receiver = it.arguments[0] as WindowManager.KeyboardShortcutsReceiver
+ receiver.onKeyboardShortcutsReceived(shortcutGroups)
+ }
+ }
+
+ @Test
+ fun shortcutGroups_wmReturnsNullList_returnsEmptyList() =
+ testScope.runTest {
+ shortcutGroups = null
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ assertThat(groups).isEmpty()
+ }
+
+ @Test
+ fun shortcutGroups_wmReturnsEmptyList_returnsEmptyList() =
+ testScope.runTest {
+ shortcutGroups = emptyList()
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ assertThat(groups).isEmpty()
+ }
+
+ @Test
+ fun shortcutGroups_wmReturnsGroups_returnsWmGroups() =
+ testScope.runTest {
+ shortcutGroups =
+ listOf(
+ KeyboardShortcutGroup("wm ime group 1"),
+ KeyboardShortcutGroup("wm ime group 2"),
+ KeyboardShortcutGroup("wm ime group 3"),
+ )
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ assertThat(groups).hasSize(3)
+ }
+
+ companion object {
+ private const val TEST_DEVICE_ID = 9876
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
new file mode 100644
index 000000000000..715d907b7372
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager.KeyboardShortcutsReceiver
+import android.view.mockWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputShortcutsSourceTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mockWindowManager = kosmos.mockWindowManager
+ private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager)
+
+ private var wmImeShortcutGroups: List<KeyboardShortcutGroup>? = null
+
+ @Before
+ fun setUp() {
+ whenever(mockWindowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
+ val receiver = it.arguments[0] as KeyboardShortcutsReceiver
+ receiver.onKeyboardShortcutsReceived(wmImeShortcutGroups)
+ }
+ }
+
+ @Test
+ fun shortcutGroups_wmReturnsNullList_returnsSingleGroup() =
+ testScope.runTest {
+ wmImeShortcutGroups = null
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ assertThat(groups).hasSize(1)
+ }
+
+ @Test
+ fun shortcutGroups_wmReturnsEmptyList_returnsSingleGroup() =
+ testScope.runTest {
+ wmImeShortcutGroups = emptyList()
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ assertThat(groups).hasSize(1)
+ }
+
+ @Test
+ fun shortcutGroups_wmReturnsGroups_returnsWmGroupsPlusOne() =
+ testScope.runTest {
+ wmImeShortcutGroups =
+ listOf(
+ KeyboardShortcutGroup("wm ime group 1"),
+ KeyboardShortcutGroup("wm ime group 2"),
+ KeyboardShortcutGroup("wm ime group 3"),
+ )
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ assertThat(groups).hasSize(4)
+ }
+
+ companion object {
+ private const val TEST_DEVICE_ID = 1234
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 4f4aac454912..3b96be48b2cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -152,7 +152,6 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC
val featureFlags =
FakeFeatureFlags().apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
- set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
}
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 9fb1aa795c3c..e89abf6fc5a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -195,7 +195,6 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
val featureFlags =
FakeFeatureFlags().apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
- set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
}
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index d183c7370713..7dd802878674 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -47,13 +47,7 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: AlertDialog
private val flags = mock<FeatureFlagsClassic>()
- private val onStartRecordingClicked = mock<Runnable>()
- private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
-
- private val mediaProjectionConfig: MediaProjectionConfig =
- MediaProjectionConfig.createConfigForDefaultDisplay()
- private val appName: String = "testApp"
- private val hostUid: Int = 12345
+ private val appName = "Test App"
private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
@@ -73,16 +67,33 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
}
@Test
- fun showDialog_forceShowPartialScreenShareFalse() {
- // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
- // overrideDisableSingleAppOption = false
- val overrideDisableSingleAppOption = false
- setUpAndShowDialog(overrideDisableSingleAppOption)
+ fun showDefaultDialog() {
+ setUpAndShowDialog()
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val secondOptionText =
spinner.adapter
.getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text1)
+ ?.text
+
+ // check that the first option is single app and enabled
+ assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+ // check that the second option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), secondOptionText)
+ }
+
+ @Test
+ fun showDialog_disableSingleApp() {
+ setUpAndShowDialog(
+ mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
+ )
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val secondOptionWarningText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
.findViewById<TextView>(android.R.id.text2)
?.text
@@ -90,15 +101,15 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
// check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+ assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
}
@Test
- fun showDialog_forceShowPartialScreenShareTrue() {
- // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
- // overrideDisableSingleAppOption = true
- val overrideDisableSingleAppOption = true
- setUpAndShowDialog(overrideDisableSingleAppOption)
+ fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
+ setUpAndShowDialog(
+ mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
+ overrideDisableSingleAppOption = true
+ )
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val secondOptionText =
@@ -114,17 +125,43 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
assertEquals(context.getString(resIdFullScreen), secondOptionText)
}
- private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+ @Test
+ fun showDialog_disableSingleApp_hasCastingCapabilities() {
+ setUpAndShowDialog(
+ mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
+ hasCastingCapabilities = true
+ )
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val secondOptionWarningText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text2)
+ ?.text
+
+ // check that the first option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+ // check that the second option is single app and disabled
+ assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
+ }
+
+ private fun setUpAndShowDialog(
+ mediaProjectionConfig: MediaProjectionConfig? = null,
+ overrideDisableSingleAppOption: Boolean = false,
+ hasCastingCapabilities: Boolean = false,
+ ) {
val delegate =
MediaProjectionPermissionDialogDelegate(
context,
mediaProjectionConfig,
- {},
- onStartRecordingClicked,
+ onStartRecordingClicked = {},
+ onCancelClicked = {},
+ hasCastingCapabilities,
appName,
overrideDisableSingleAppOption,
- hostUid,
- mediaProjectionMetricsLogger
+ hostUid = 12345,
+ mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
)
dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index e46416c1b321..ebab04989590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -75,6 +75,7 @@ import com.android.systemui.qs.QsEventLoggerFake;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.StatusBarState;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -146,6 +147,12 @@ public class QSTileImplTest extends SysuiTestCase {
mTile.setTileSpec(SPEC);
}
+ @After
+ public void destroyTile() {
+ mTile.destroy();
+ mTestableLooper.processAllMessages();
+ }
+
@Test
public void testClick_Metrics() {
mTile.click(null /* expandable */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
index 25dd9fedba7c..24e8b1886438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -27,7 +27,6 @@ import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -44,7 +43,7 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() {
private val imageExporter = mock<ImageExporter>()
private val smartActions = mock<ScreenshotSmartActions>()
private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
- private val saveImageData = SaveImageInBackgroundData()
+ private val saveImageData = SaveImageInBackgroundTask.SaveImageInBackgroundData()
private val testScreenshotId: String = "testScreenshotId"
private val testBitmap = mock<Bitmap>()
private val testUser = UserHandle.getUserHandleForUid(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index 3b0e194ce223..bab9bbbfde4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -16,13 +16,13 @@
package com.android.systemui.screenshot.policy
import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.graphics.Insets
import android.graphics.Rect
import android.os.UserHandle
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.screenshot.ImageCapture
import com.android.systemui.screenshot.ScreenshotData
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -40,20 +40,23 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PolicyRequestProcessorTest {
- val imageCapture = object : ImageCapture {
- override fun captureDisplay(displayId: Int, crop: Rect?) = null
- override suspend fun captureTask(taskId: Int) = null
- }
+ val imageCapture =
+ object : ImageCapture {
+ override fun captureDisplay(displayId: Int, crop: Rect?) = null
+
+ override suspend fun captureTask(taskId: Int) = null
+ }
/** Tests behavior when no policies are applied */
@Test
fun testProcess_defaultOwner_whenNoPolicyApplied() {
val fullScreenWork = DisplayContentRepository {
- singleFullScreen(TaskSpec(taskId = 1001, name = FILES, userId = WORK))
+ singleFullScreen(TaskSpec(taskId = TASK_ID, name = FILES, userId = WORK))
}
val request =
- ScreenshotData(TAKE_SCREENSHOT_FULLSCREEN,
+ ScreenshotData(
+ TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_KEY_CHORD,
null,
topComponent = null,
@@ -61,24 +64,34 @@ class PolicyRequestProcessorTest {
taskId = -1,
insets = Insets.NONE,
bitmap = null,
- displayId = DEFAULT_DISPLAY)
+ displayId = DEFAULT_DISPLAY
+ )
/* Create a policy request processor with no capture policies */
val requestProcessor =
- PolicyRequestProcessor(Dispatchers.Unconfined,
+ PolicyRequestProcessor(
+ Dispatchers.Unconfined,
imageCapture,
policies = emptyList(),
defaultOwner = UserHandle.of(PERSONAL),
defaultComponent = ComponentName("default", "Component"),
- displayTasks = fullScreenWork)
+ displayTasks = fullScreenWork
+ )
val result = runBlocking { requestProcessor.process(request) }
- assertWithMessage(
- "With no policy, the screenshot should be assigned to the default user"
- ).that(result.userHandle).isEqualTo(UserHandle.of(PERSONAL))
+ assertWithMessage("With no policy, the screenshot should be assigned to the default user")
+ .that(result.userHandle)
+ .isEqualTo(UserHandle.of(PERSONAL))
+
+ assertWithMessage("The topComponent of the screenshot")
+ .that(result.topComponent)
+ .isEqualTo(ComponentName.unflattenFromString(FILES))
+
+ assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
+ }
- assertWithMessage("The topComponent of the screenshot").that(result.topComponent)
- .isEqualTo(ComponentName.unflattenFromString(FILES))
+ companion object {
+ const val TASK_ID = 1001
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 169511f827ef..967df39c9269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -48,7 +48,12 @@ import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -67,13 +72,13 @@ import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyFloat
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@@ -87,11 +92,11 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
testDispatcher = UnconfinedTestDispatcher()
}
- @Mock private lateinit var communalViewModel: CommunalViewModel
- @Mock private lateinit var powerManager: PowerManager
- @Mock private lateinit var touchMonitor: TouchMonitor
- @Mock private lateinit var communalColors: CommunalColors
- @Mock private lateinit var communalContent: CommunalContent
+ private var communalViewModel = mock<CommunalViewModel>()
+ private var powerManager = mock<PowerManager>()
+ private var touchMonitor = mock<TouchMonitor>()
+ private var communalColors = mock<CommunalColors>()
+ private var communalContent = mock<CommunalContent>()
private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory
private lateinit var parentView: FrameLayout
@@ -103,8 +108,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
-
communalRepository = kosmos.fakeCommunalSceneRepository
ambientTouchComponentFactory =
@@ -124,6 +127,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
communalInteractor,
communalViewModel,
keyguardInteractor,
+ kosmos.keyguardTransitionInteractor,
shadeInteractor,
powerManager,
communalColors,
@@ -167,6 +171,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
communalInteractor,
communalViewModel,
keyguardInteractor,
+ kosmos.keyguardTransitionInteractor,
shadeInteractor,
powerManager,
communalColors,
@@ -192,6 +197,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
communalInteractor,
communalViewModel,
keyguardInteractor,
+ kosmos.keyguardTransitionInteractor,
shadeInteractor,
powerManager,
communalColors,
@@ -212,6 +218,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
communalInteractor,
communalViewModel,
keyguardInteractor,
+ kosmos.keyguardTransitionInteractor,
shadeInteractor,
powerManager,
communalColors,
@@ -235,12 +242,15 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
- fun lifecycle_resumedAfterCommunalShows() {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ fun lifecycle_resumedAfterCommunalShows() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+ }
@Test
fun lifecycle_startedAfterCommunalCloses() =
@@ -289,6 +299,43 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
+ fun lifecycle_startedWhenEditActivityShowing() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Edit activity is showing.
+ communalInteractor.setEditActivityShowing(true)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+ }
+
+ @Test
+ fun lifecycle_startedWhenEditModeTransitionStarted() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Leaving edit mode to return to the hub.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1.0f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+ }
+
+ @Test
fun lifecycle_createdAfterDisposeView() {
// Container view disposed.
underTest.disposeView()
@@ -312,6 +359,59 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
+ fun lifecycle_doesNotResumeOnUserInteractivityOnceExpanded() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ shadeTestUtil.setShadeExpansion(1.0f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+ // Shade starts collapsing.
+ shadeTestUtil.setShadeExpansion(.5f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+ // Shade fully collpase, and then expand should with touch interaction should now
+ // be resumed.
+ shadeTestUtil.setShadeExpansion(0f)
+ testableLooper.processAllMessages()
+ shadeTestUtil.setShadeExpansion(.5f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+ }
+
+ @Test
+ fun touchHandling_moveEventProcessedAfterCancel() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ shadeTestUtil.setQsExpansion(0.5f)
+ testableLooper.processAllMessages()
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+ }
+ }
+
+ @Test
fun editMode_communalAvailable() =
with(kosmos) {
testScope.runTest {
@@ -433,10 +533,10 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
testScope.runTest {
// Communal is closed.
goToScene(CommunalScenes.Blank)
- `when`(
+ whenever(
notificationStackScrollLayoutController.isBelowLastNotification(
- anyFloat(),
- anyFloat()
+ any(),
+ any()
)
)
.thenReturn(false)
@@ -444,6 +544,62 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @Test
+ fun onTouchEvent_hubOpen_touchesDispatched() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Touch event is sent to the container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView).onTouchEvent(any())
+ }
+ }
+
+ @Test
+ fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Transitioning to or from edit mode.
+ communalInteractor.setEditActivityShowing(true)
+ testableLooper.processAllMessages()
+
+ // onTouchEvent returns true to consume the touch, but it is not sent to the
+ // container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView, never()).onTouchEvent(any())
+ }
+ }
+
+ @Test
+ fun onTouchEvent_editModeTransitionStarted_touchesConsumedButNotDispatched() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Leaving edit mode to return to the hub.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1.0f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ testableLooper.processAllMessages()
+
+ // onTouchEvent returns true to consume the touch, but it is not sent to the
+ // container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView, never()).onTouchEvent(any())
+ }
+ }
+
private fun initAndAttachContainerView() {
val mockInsets =
mock<WindowInsets> {
@@ -462,8 +618,21 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
testableLooper.processAllMessages()
}
- private fun goToScene(scene: SceneKey) {
+ private suspend fun goToScene(scene: SceneKey) {
communalRepository.changeScene(scene)
+ if (scene == CommunalScenes.Communal) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ kosmos.testScope
+ )
+ } else {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ kosmos.testScope
+ )
+ }
testableLooper.processAllMessages()
}
@@ -488,5 +657,35 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
CONTAINER_HEIGHT.toFloat() / 2,
0
)
+
+ private val CANCEL_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_CANCEL,
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
+ 0
+ )
+
+ private val MOVE_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_MOVE,
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
+ 0
+ )
+
+ private val UP_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_UP,
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
+ 0
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8a6b68f26f66..a5c4bcd46a1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -675,8 +675,14 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mMainHandler = new Handler(Looper.getMainLooper());
+ LongPressHandlingView longPressHandlingView = mock(LongPressHandlingView.class);
when(mView.requireViewById(R.id.keyguard_long_press))
- .thenReturn(mock(LongPressHandlingView.class));
+ .thenReturn(longPressHandlingView);
+
+ Resources longPressHandlingViewRes = mock(Resources.class);
+ when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes);
+ when(longPressHandlingViewRes.getString(anyInt())).thenReturn("");
+
mHeadsUpNotificationInteractor =
new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 52af907c7b7d..64eadb7db1e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -36,7 +36,6 @@ import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index acb005f6e72f..0407fc14d35a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -42,7 +42,7 @@ import org.mockito.quality.Strictness
@RunWith(AndroidJUnit4::class)
@SmallTest
// this class has no testable logic with either of these flags enabled
-@DisableFlags(PriorityPeopleSection.FLAG_NAME, NotificationMinimalismPrototype.V2.FLAG_NAME)
+@DisableFlags(PriorityPeopleSection.FLAG_NAME, NotificationMinimalismPrototype.FLAG_NAME)
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
lateinit var manager: NotificationSectionsFeatureManager
private val proxyFake = DeviceConfigProxyFake()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a925ccfe174b..b7995953b327 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -91,6 +91,7 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -1196,6 +1197,26 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // Entry was seen in shade
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isSeenInShade()).thenReturn(true);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ when(row.getEntry()).thenReturn(entry);
+
+ // WHEN we generate an add event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // THEN nothing happens
+ assertThat(mStackScroller.isAddOrRemoveAnimationPending()).isFalse();
+ }
+
+ @Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
// GIVEN NSSL is ready for HUN animations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index cc2ef53c6cdb..12cfdcfa8df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -35,7 +35,6 @@ import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInterac
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.batteryController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
index f40282f0da69..a8271fec0963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt
@@ -21,10 +21,9 @@ import android.view.InputDevice.SOURCE_MOUSE
import android.view.MotionEvent
import android.view.MotionEvent.CLASSIFICATION_NONE
import android.view.MotionEvent.TOOL_TYPE_FINGER
-import java.lang.reflect.Method
-import org.mockito.kotlin.doNothing
+import org.mockito.AdditionalAnswers
+import org.mockito.Mockito.mock
import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
fun motionEvent(
@@ -40,31 +39,18 @@ fun motionEvent(
val event =
MotionEvent.obtain(/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0)
event.source = source
- val spy =
- spy<MotionEvent>(event) {
- on { getToolType(0) } doReturn toolType
- on { getPointerCount() } doReturn pointerCount
- axisValues.forEach { (key, value) -> on { getAxisValue(key) } doReturn value }
- on { getClassification() } doReturn classification
- }
- ensureFinalizeIsNotCalledTwice(spy)
- return spy
-}
-
-private fun ensureFinalizeIsNotCalledTwice(spy: MotionEvent) {
- // Spy in mockito will create copy of the spied object, copying all its field etc. Here it means
- // we create copy of MotionEvent and its mNativePtr, so we have two separate objects of type
- // MotionEvents with the same mNativePtr. That breaks because MotionEvent has custom finalize()
- // method which goes to native code and tries to delete the reference from mNativePtr. It works
- // first time but second time reference is already deleted and it breaks. That's why we have to
- // avoid calling finalize twice
- doNothing().whenever(spy).finalizeUsingReflection()
-}
-
-private fun MotionEvent.finalizeUsingReflection() {
- val finalizeMethod: Method = MotionEvent::class.java.getDeclaredMethod("finalize")
- finalizeMethod.isAccessible = true
- finalizeMethod.invoke(this)
+ // we need to use mock with delegation instead of spy because:
+ // 1. Spy will try to deallocate the same memory again when finalize() is called as it keep the
+ // same memory pointer to native MotionEvent
+ // 2. Even after workaround for issue above there still remains problem with destructor of
+ // native event trying to free the same chunk of native memory. I'm not sure why it happens but
+ // mock seems to fix the issue and because it delegates all calls seems safer overall
+ val delegate = mock(MotionEvent::class.java, AdditionalAnswers.delegatesTo<MotionEvent>(event))
+ doReturn(toolType).whenever(delegate).getToolType(0)
+ doReturn(pointerCount).whenever(delegate).pointerCount
+ doReturn(classification).whenever(delegate).classification
+ axisValues.forEach { (key, value) -> doReturn(value).whenever(delegate).getAxisValue(key) }
+ return delegate
}
fun touchpadEvent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index f7371487a7c5..3f5dc82ec5cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -18,6 +18,8 @@ package com.android.systemui.volume;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -37,16 +39,19 @@ import android.media.IAudioService;
import android.media.session.MediaSession;
import android.os.Handler;
import android.os.Process;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.settingslib.flags.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
@@ -54,7 +59,9 @@ import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
import org.junit.Before;
import org.junit.Test;
@@ -63,6 +70,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Objects;
import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
@@ -104,6 +112,10 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
private UserTracker mUserTracker;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private AudioSharingInteractor mAudioSharingInteractor;
+ @Mock
+ private JavaAdapter mJavaAdapter;
@Before
@@ -124,11 +136,26 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
mCallback = mock(VolumeDialogControllerImpl.C.class);
mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
- mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
- mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
- mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
- mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
- mActivityManager, mUserTracker, mDumpManager, mCallback);
+ mVolumeController =
+ new TestableVolumeDialogControllerImpl(
+ mContext,
+ mBroadcastDispatcher,
+ mRingerModeTracker,
+ mThreadFactory,
+ mAudioManager,
+ mNotificationManager,
+ mVibrator,
+ mIAudioService,
+ mAccessibilityManager,
+ mPackageManager,
+ mWakefullnessLifcycle,
+ mKeyguardManager,
+ mActivityManager,
+ mUserTracker,
+ mDumpManager,
+ mCallback,
+ mAudioSharingInteractor,
+ mJavaAdapter);
mVolumeController.setEnableDialogs(true, true);
}
@@ -224,6 +251,41 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
}
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ public void handleAudioSharingStreamVolumeChanges_updateState() {
+ ArgumentCaptor<VolumeDialogController.State> stateCaptor =
+ ArgumentCaptor.forClass(VolumeDialogController.State.class);
+ int broadcastStream = VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
+
+ mVolumeController.handleAudioSharingStreamVolumeChanges(100);
+
+ verify(mCallback).onStateChanged(stateCaptor.capture());
+ assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isTrue();
+ assertThat(stateCaptor.getValue().states.get(broadcastStream).level).isEqualTo(100);
+
+ mVolumeController.handleAudioSharingStreamVolumeChanges(200);
+
+ verify(mCallback, times(2)).onStateChanged(stateCaptor.capture());
+ assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isTrue();
+ assertThat(stateCaptor.getValue().states.get(broadcastStream).level).isEqualTo(200);
+
+ mVolumeController.handleAudioSharingStreamVolumeChanges(null);
+
+ verify(mCallback, times(3)).onStateChanged(stateCaptor.capture());
+ assertThat(stateCaptor.getValue().states.contains(broadcastStream)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ public void testSetStreamVolume_setSecondaryDeviceVolume() {
+ mVolumeController.setStreamVolume(
+ VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST, /* level= */ 100);
+ Objects.requireNonNull(TestableLooper.get(this)).processAllMessages();
+
+ verify(mAudioSharingInteractor).setStreamVolume(100);
+ }
+
static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
private final WakefulnessLifecycle.Observer mWakefullessLifecycleObserver;
@@ -243,11 +305,27 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
ActivityManager activityManager,
UserTracker userTracker,
DumpManager dumpManager,
- C callback) {
- super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
- notificationManager, optionalVibrator, iAudioService, accessibilityManager,
- packageManager, wakefulnessLifecycle, keyguardManager,
- activityManager, userTracker, dumpManager);
+ C callback,
+ AudioSharingInteractor audioSharingInteractor,
+ JavaAdapter javaAdapter) {
+ super(
+ context,
+ broadcastDispatcher,
+ ringerModeTracker,
+ theadFactory,
+ audioManager,
+ notificationManager,
+ optionalVibrator,
+ iAudioService,
+ accessibilityManager,
+ packageManager,
+ wakefulnessLifecycle,
+ keyguardManager,
+ activityManager,
+ userTracker,
+ dumpManager,
+ audioSharingInteractor,
+ javaAdapter);
mCallbacks = callback;
ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index cdfcca6c7065..b5cbf598bd05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -23,6 +23,7 @@ import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
+import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertEquals;
@@ -72,6 +73,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.settingslib.flags.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
@@ -794,6 +796,38 @@ public class VolumeDialogImplTest extends SysuiTestCase {
verify(mVolumeDialogInteractor, atLeastOnce()).onDialogDismissed(); // dismiss by timeout
}
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ public void testDynamicStreamForBroadcast_createRow() {
+ State state = createShellState();
+ VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
+ ss.dynamic = true;
+ ss.levelMin = 0;
+ ss.levelMax = 255;
+ ss.level = 20;
+ ss.name = -1;
+ ss.remoteLabel = mContext.getString(R.string.audio_sharing_description);
+ state.states.append(DYNAMIC_STREAM_BROADCAST, ss);
+
+ mDialog.onStateChangedH(state);
+ mTestableLooper.processAllMessages();
+
+ ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows);
+ assumeNotNull(volumeDialogRows);
+ View broadcastRow = null;
+ final int rowCount = volumeDialogRows.getChildCount();
+ // we don't make assumptions about the position of the dnd row
+ for (int i = 0; i < rowCount; i++) {
+ View volumeRow = volumeDialogRows.getChildAt(i);
+ if (volumeRow.getId() == DYNAMIC_STREAM_BROADCAST) {
+ broadcastRow = volumeRow;
+ break;
+ }
+ }
+ assertNotNull(broadcastRow);
+ assertEquals(broadcastRow.getVisibility(), View.VISIBLE);
+ }
+
/**
* @return true if at least one volume row has the DND icon
*/
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt
new file mode 100644
index 000000000000..2050437da38a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalSceneTransitionRepository: CommunalSceneTransitionRepository by
+ Kosmos.Fixture { CommunalSceneTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
index 1da1fb2ee52f..5e870b19681b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -25,20 +25,11 @@ import kotlinx.coroutines.flow.map
/** Fake implementation of [CommunalPrefsRepository] */
class FakeCommunalPrefsRepository : CommunalPrefsRepository {
private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
- private val _isDisclaimerDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
_isCtaDismissed.map { it.contains(user) }
- override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> =
- _isDisclaimerDismissed.map { it.contains(user) }
-
override suspend fun setCtaDismissed(user: UserInfo) {
_isCtaDismissed.value = _isCtaDismissed.value.toMutableSet().apply { add(user) }
}
-
- override suspend fun setDisclaimerDismissed(user: UserInfo) {
- _isDisclaimerDismissed.value =
- _isDisclaimerDismissed.value.toMutableSet().apply { add(user) }
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
index d280be216f5e..82454817ecbb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
@@ -6,7 +6,6 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.communal.shared.model.CommunalScenes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -25,11 +24,10 @@ class FakeCommunalSceneRepository(
) : CommunalSceneRepository {
override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) =
- snapToScene(toScene, 0)
+ snapToScene(toScene)
- override fun snapToScene(toScene: SceneKey, delayMillis: Long) {
+ override fun snapToScene(toScene: SceneKey) {
applicationScope.launch {
- delay(delayMillis)
currentScene.value = toScene
_transitionState.value = flowOf(ObservableTransitionState.Idle(toScene))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index eb9278537db5..4ad046cc095e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -42,6 +43,7 @@ val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
applicationScope = applicationCoroutineScope,
bgDispatcher = testDispatcher,
+ bgScope = testScope.backgroundScope,
broadcastDispatcher = broadcastDispatcher,
communalSceneInteractor = communalSceneInteractor,
widgetRepository = communalWidgetRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..e6e59e1a523e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalSceneTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.internalKeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.communalSceneTransitionInteractor: CommunalSceneTransitionInteractor by
+ Kosmos.Fixture {
+ CommunalSceneTransitionInteractor(
+ applicationScope = applicationCoroutineScope,
+ transitionInteractor = keyguardTransitionInteractor,
+ internalTransitionInteractor = internalKeyguardTransitionInteractor,
+ settingsInteractor = communalSettingsInteractor,
+ sceneInteractor = communalSceneInteractor,
+ repository = communalSceneTransitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index f73f43dc53fe..edf4bcc238c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,7 +17,10 @@
package com.android.systemui.education.data.repository
import com.android.systemui.kosmos.Kosmos
+import java.time.Clock
import java.time.Instant
var Kosmos.contextualEducationRepository: ContextualEducationRepository by
- Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
+ Kosmos.Fixture { FakeContextualEducationRepository(fakeEduClock) }
+
+var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
new file mode 100644
index 000000000000..5b2dc2b39e27
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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.systemui.education.domain.interactor
+
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.contextualEducationInteractor by
+ Kosmos.Fixture {
+ ContextualEducationInteractor(
+ backgroundScope = testScope.backgroundScope,
+ repository = contextualEducationRepository,
+ selectedUserInteractor = selectedUserInteractor
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
new file mode 100644
index 000000000000..8f84e0482b83
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 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.systemui.education.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+var Kosmos.keyboardTouchpadEduStatsInteractor by
+ Kosmos.Fixture {
+ KeyboardTouchpadEduStatsInteractorImpl(
+ backgroundScope = testScope.backgroundScope,
+ contextualEducationInteractor = contextualEducationInteractor
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
index 079852a641c2..494f08b88345 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@ var Kosmos.fromGlanceableHubTransitionInteractor by
transitionInteractor = keyguardTransitionInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ communalSceneInteractor = communalSceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
index c2169456eac5..7827655806d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -37,5 +38,6 @@ val Kosmos.fromOccludedTransitionInteractor by
powerInteractor = powerInteractor,
communalInteractor = communalInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ communalSceneInteractor = communalSceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
index da956ec67696..8b4de2bcc26f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
@@ -22,13 +22,12 @@ package com.android.systemui.statusbar.notification.collection
* The [modifier] function will be passed an instance of a NotificationEntryBuilder. Any
* modifications made to the builder will be applied to the [entry].
*/
-inline fun modifyEntry(
- entry: NotificationEntry,
+inline fun NotificationEntry.modifyEntry(
crossinline modifier: NotificationEntryBuilder.() -> Unit
) {
- val builder = NotificationEntryBuilder(entry)
+ val builder = NotificationEntryBuilder(this)
modifier(builder)
- builder.apply(entry)
+ builder.apply(this)
}
fun getAttachState(entry: ListEntry): ListAttachState {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
new file mode 100644
index 000000000000..77d97bb7cbe9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
+import com.android.systemui.util.settings.fakeSettings
+
+var Kosmos.lockScreenMinimalismCoordinator by
+ Kosmos.Fixture {
+ LockScreenMinimalismCoordinator(
+ bgDispatcher = testDispatcher,
+ dumpManager = dumpManager,
+ headsUpInteractor = headsUpNotificationInteractor,
+ logger = lockScreenMinimalismCoordinatorLogger,
+ scope = testScope.backgroundScope,
+ secureSettings = fakeSettings,
+ seenNotificationsInteractor = seenNotificationsInteractor,
+ statusBarStateController = statusBarStateController,
+ shadeInteractor = shadeInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt
new file mode 100644
index 000000000000..77aeb44e15eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.lockScreenMinimalismCoordinatorLogger by
+ Kosmos.Fixture { LockScreenMinimalismCoordinatorLogger(logcatLogBuffer()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 492e87bbcac4..7e8f1a9115ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -22,14 +22,19 @@ import com.android.systemui.statusbar.notification.data.repository.HeadsUpReposi
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
val Kosmos.headsUpNotificationRepository by Fixture { FakeHeadsUpNotificationRepository() }
class FakeHeadsUpNotificationRepository : HeadsUpRepository {
override val isHeadsUpAnimatingAway: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
- override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
- MutableStateFlow(emptySet())
+
+ val orderedHeadsUpRows = MutableStateFlow(emptyList<HeadsUpRowRepository>())
+ override val topHeadsUpRow: Flow<HeadsUpRowRepository?> =
+ orderedHeadsUpRows.map { it.firstOrNull() }.distinctUntilChanged()
+ override val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> =
+ orderedHeadsUpRows.map { it.toSet() }.distinctUntilChanged()
override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
isHeadsUpAnimatingAway.value = animatingAway
@@ -38,4 +43,12 @@ class FakeHeadsUpNotificationRepository : HeadsUpRepository {
override fun snooze() {
// do nothing
}
+
+ fun setNotifications(notifications: List<HeadsUpRowRepository>) {
+ this.orderedHeadsUpRows.value = notifications.toList()
+ }
+
+ fun setNotifications(vararg notifications: HeadsUpRowRepository) {
+ this.orderedHeadsUpRows.value = notifications.toList()
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index 9247e889f8f9..e3176f1783e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -26,9 +26,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
private val _isDeviceProvisioned = MutableStateFlow(true)
override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
+
fun setDeviceProvisioned(isProvisioned: Boolean) {
_isDeviceProvisioned.value = isProvisioned
}
+
+ override fun isDeviceProvisioned(): Boolean {
+ return _isDeviceProvisioned.value
+ }
}
@Module
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index b4efae3a05e4..8e2e0ad76d15 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -114,6 +114,13 @@ flag {
}
flag {
+ name: "enable_magnification_follows_mouse"
+ namespace: "accessibility"
+ description: "Whether to enable mouse following for fullscreen magnification"
+ bug: "335494097"
+}
+
+flag {
name: "fix_drag_pointer_when_ending_drag"
namespace: "accessibility"
description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b918d80fc63d..30c743e508b6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -161,6 +161,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.AccessibilityShortcutController.ExtraDimFrameworkFeatureInfo;
import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
import com.android.internal.accessibility.common.ShortcutConstants;
@@ -3910,6 +3911,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
return;
}
+
// In case user assigned an accessibility framework feature to the given shortcut.
if (performAccessibilityFrameworkFeature(displayId, targetComponentName, shortcutType)) {
return;
@@ -3933,6 +3935,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (!frameworkFeatureMap.containsKey(assignedTarget)) {
return false;
}
+ final int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ }
final FrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(assignedTarget);
final SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(),
featureInfo.getSettingKey(), mCurrentUserId);
@@ -3944,6 +3950,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return true;
}
+ if (featureInfo instanceof ExtraDimFrameworkFeatureInfo) {
+ boolean serviceEnabled =
+ ((ExtraDimFrameworkFeatureInfo) featureInfo)
+ .activateShortcut(mContext, userId);
+ logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
+ serviceEnabled);
+ return true;
+ }
+
// Assuming that the default state will be to have the feature off
if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) {
logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index ad8f5e1481e1..668852b46ea2 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -31,12 +31,14 @@ import android.hardware.display.DisplayManager;
import android.metrics.LogMaker;
import android.os.UserManager;
import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
import android.service.autofill.InternalSanitizer;
import android.service.autofill.SaveInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
@@ -374,4 +376,50 @@ public final class Helper {
private interface ViewNodeFilter {
boolean matches(ViewNode node);
}
+
+ public static class SaveInfoStats {
+ public int saveInfoCount;
+ public int saveDataTypeCount;
+
+ public SaveInfoStats(int saveInfoCount, int saveDataTypeCount) {
+ this.saveInfoCount = saveInfoCount;
+ this.saveDataTypeCount = saveDataTypeCount;
+ }
+ }
+
+ /**
+ * Get statistic information of save info given a sparse array of fill responses.
+ *
+ * Specifically the statistic includes
+ * 1. how many save info the current session has.
+ * 2. How many distinct save data types current session has.
+ *
+ * @return SaveInfoStats returns the above two number in a SaveInfoStats object
+ */
+ public static SaveInfoStats getSaveInfoStatsFromFillResponses(
+ SparseArray<FillResponse> fillResponses) {
+ if (fillResponses == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "getSaveInfoStatsFromFillResponses(): fillResponse sparse array is "
+ + "null");
+ }
+ return new SaveInfoStats(-1, -1);
+ }
+ int numSaveInfos = 0;
+ int numSaveDataTypes = 0;
+ ArraySet<Integer> saveDataTypeSeen = new ArraySet<>();
+ final int numResponses = fillResponses.size();
+ for (int responseNum = 0; responseNum < numResponses; responseNum++) {
+ final FillResponse response = fillResponses.valueAt(responseNum);
+ if (response != null && response.getSaveInfo() != null) {
+ numSaveInfos += 1;
+ int saveDataType = response.getSaveInfo().getType();
+ if (!saveDataTypeSeen.contains(saveDataType)) {
+ saveDataTypeSeen.add(saveDataType);
+ numSaveDataTypes += 1;
+ }
+ }
+ }
+ return new SaveInfoStats(numSaveInfos, numSaveDataTypes);
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index a5ec2ba2f267..49ca29745b11 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -854,7 +854,6 @@ public final class PresentationStatsEventLogger {
mCallingAppUid,
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
- event.mFilteredFillabaleViewCount,
event.mViewFillableTotalCount,
event.mViewFillFailureCount,
event.mFocusedId,
@@ -868,6 +867,7 @@ public final class PresentationStatsEventLogger {
event.mFocusedVirtualAutofillId,
event.mFieldFirstLength,
event.mFieldLastLength,
+ event.mFilteredFillabaleViewCount,
event.mViewFailedPriorToRefillCount,
event.mViewFilledSuccessfullyOnRefillCount,
event.mViewFailedOnRefillCount,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c6ddc16211bc..21df7a5487ef 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -70,6 +70,7 @@ import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATU
import static com.android.server.autofill.Helper.containsCharsInOrder;
import static com.android.server.autofill.Helper.createSanitizers;
import static com.android.server.autofill.Helper.getNumericValue;
+import static com.android.server.autofill.Helper.SaveInfoStats;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import static com.android.server.autofill.Helper.toArray;
@@ -3203,11 +3204,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return saveInfo == null ? 0 : saveInfo.getFlags();
}
- static class SaveInfoStats {
- public int saveInfoCount;
- public int saveDataTypeCount;
- }
-
/**
* Get statistic information of save info in current session. Specifically
* 1. how many save info the current session has.
@@ -3217,42 +3213,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*/
@GuardedBy("mLock")
private SaveInfoStats getSaveInfoStatsLocked() {
- SaveInfoStats retSaveInfoStats = new SaveInfoStats();
- retSaveInfoStats.saveInfoCount = -1;
- retSaveInfoStats.saveDataTypeCount = -1;
-
if (mContexts == null) {
if (sVerbose) {
Slog.v(TAG, "getSaveInfoStatsLocked(): mContexts is null");
}
- } else if (mResponses == null) {
- // Happens when the activity / session was finished before the service replied, or
- // when the service cannot autofill it (and returned a null response).
- if (sVerbose) {
- Slog.v(TAG, "getSaveInfoStatsLocked(): mResponses is null");
- }
- return retSaveInfoStats;
- } else {
- int numSaveInfos = 0;
- int numSaveDataTypes = 0;
- ArraySet<Integer> saveDataTypeSeen = new ArraySet<>();
- final int numResponses = mResponses.size();
- for (int responseNum = 0; responseNum < numResponses; responseNum++) {
- final FillResponse response = mResponses.valueAt(responseNum);
- if (response != null && response.getSaveInfo() != null) {
- numSaveInfos += 1;
- int saveDataType = response.getSaveInfo().getType();
- if (!saveDataTypeSeen.contains(saveDataType)) {
- saveDataTypeSeen.add(saveDataType);
- numSaveDataTypes += 1;
- }
- }
- }
- retSaveInfoStats.saveInfoCount = numSaveInfos;
- retSaveInfoStats.saveDataTypeCount = numSaveDataTypes;
+ return new SaveInfoStats(-1, -1);
}
-
- return retSaveInfoStats;
+ return Helper.getSaveInfoStatsFromFillResponses(mResponses);
}
/**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 3d53deb8d2bb..4fc9d55d3e17 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -103,9 +103,10 @@ class CompanionDeviceShellCommand extends ShellCommand {
String packageName = getNextArgRequired();
String address = getNextArgRequired();
String deviceProfile = getNextArg();
+ boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
/* callback */ null, /* resultReceiver */ null);
}
break;
@@ -462,6 +463,17 @@ class CompanionDeviceShellCommand extends ShellCommand {
}
}
+ private boolean getNextBooleanArg() {
+ String arg = getNextArg();
+ if (arg == null || "false".equalsIgnoreCase(arg)) {
+ return false;
+ } else if ("true".equalsIgnoreCase(arg)) {
+ return Boolean.parseBoolean(arg);
+ } else {
+ throw new IllegalArgumentException("Expected a boolean argument but was: " + arg);
+ }
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -470,7 +482,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
pw.println(" Print this help text.");
pw.println(" list USER_ID");
pw.println(" List all Associations for a user.");
- pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE]");
+ pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE] [SELF_MANAGED]");
pw.println(" Create a new Association.");
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 7d9d660af536..ee7d0aef2189 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -28,9 +28,9 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
-import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix;
+import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -561,8 +561,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
- options.setPendingIntentBackgroundActivityLaunchAllowed(true);
- options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
pendingIntent.send(
mContext,
/* code= */ 0,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 69ee8fc831f4..cf0befaab98d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16961,16 +16961,24 @@ public class ActivityManagerService extends IActivityManager.Stub
int userId = UserHandle.getCallingUserId();
- if (UserManager.isVisibleBackgroundUsersEnabled() && userId != getCurrentUserId()) {
- // The check is added mainly for auto devices. On auto devices, it is possible that
- // multiple users are visible simultaneously using visible background users.
- // In such cases, it is desired that only the current user (not the visible background
- // user) can change the locale and other persistent settings of the device.
- Slog.w(TAG, "Only current user is allowed to update persistent configuration if "
- + "visible background users are enabled. Current User" + getCurrentUserId()
- + ". Calling User: " + userId);
- throw new SecurityException("Only current user is allowed to update persistent "
- + "configuration.");
+ if (UserManager.isVisibleBackgroundUsersEnabled()) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ if (userId != getCurrentUserId()) {
+ // The check is added mainly for auto devices. On auto devices, it is
+ // possible that multiple users are visible simultaneously using visible
+ // background users. In such cases, it is desired that only the current user
+ // (not the visible background user) can change the locale and other persistent
+ // settings of the device.
+ Slog.w(TAG, "Only current user is allowed to update persistent configuration "
+ + "if visible background users are enabled. Current User"
+ + getCurrentUserId() + ". Calling User: " + userId);
+ throw new SecurityException("Only current user is allowed to update persistent "
+ + "configuration.");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
mActivityTaskManager.updatePersistentConfiguration(values, userId);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 061bcd740f6b..ee7033e4a437 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -47,6 +47,7 @@ import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -63,6 +64,8 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerService;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.wm.WindowProcessController;
@@ -868,9 +871,6 @@ class AppErrors {
private boolean handleAppCrashLSPB(ProcessRecord app, String reason,
String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
final long now = SystemClock.uptimeMillis();
- final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.ANR_SHOW_BACKGROUND, 0,
- mService.mUserController.getCurrentUserId()) != 0;
Long crashTime;
Long crashTimePersistent;
@@ -881,6 +881,8 @@ class AppErrors {
final boolean persistent = app.isPersistent();
final WindowProcessController proc = app.getWindowProcessController();
final ProcessErrorStateRecord errState = app.mErrorState;
+ final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND, 0, getVisibleUserId(userId)) != 0;
if (!app.isolated) {
crashTime = mProcessCrashTimes.get(processName, uid);
@@ -1000,9 +1002,6 @@ class AppErrors {
void handleShowAppErrorUi(Message msg) {
AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
- boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.ANR_SHOW_BACKGROUND, 0,
- mService.mUserController.getCurrentUserId()) != 0;
final int userId;
synchronized (mProcLock) {
@@ -1027,7 +1026,11 @@ class AppErrors {
for (int profileId : mService.mUserController.getCurrentProfileIds()) {
isBackground &= (userId != profileId);
}
- if (isBackground && !showBackground) {
+ int visibleUserId = getVisibleUserId(userId);
+ boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId);
+ boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
+ if (isBackground && !showBackground && !isVisibleUser) {
Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
if (res != null) {
res.set(AppErrorDialog.BACKGROUND_USER);
@@ -1054,7 +1057,7 @@ class AppErrors {
final long now = SystemClock.uptimeMillis();
final boolean shouldThottle = crashShowErrorTime != null
&& now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
- if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
+ if ((mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground)
&& !crashSilenced && !shouldThottle
&& (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
@@ -1103,10 +1106,10 @@ class AppErrors {
return;
}
+ int visibleUserId = getVisibleUserId(proc.userId);
boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.ANR_SHOW_BACKGROUND, 0,
- mService.mUserController.getCurrentUserId()) != 0;
- if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
+ Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
+ if (mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground) {
AnrController anrController = errState.getDialogController().getAnrController();
if (anrController == null) {
errState.getDialogController().showAnrDialogs(data);
@@ -1163,6 +1166,43 @@ class AppErrors {
}
/**
+ * Returns the user ID of the visible user associated with the error occurrence.
+ *
+ * <p>For most cases it will return the current foreground user ID, but on devices that
+ * {@link UserManager#isVisibleBackgroundUsersEnabled() support visible background users},
+ * it will return the given app user ID passed as parameter.
+ *
+ * @param appUserId The user ID of the app where the error occurred.
+ * @return The ID of the visible user associated with the error.
+ */
+ private int getVisibleUserId(int appUserId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return mService.mUserController.getCurrentUserId();
+ }
+ return appUserId;
+ }
+
+ /**
+ * Checks if the given user is a visible background user, which is a full, background user
+ * assigned to secondary displays on the devices that have
+ * {@link UserManager#isVisibleBackgroundUsersEnabled()
+ * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
+ *
+ * @see UserManager#isUserVisible()
+ */
+ private boolean isVisibleBackgroundUser(int userId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return false;
+ }
+ boolean isForeground = mService.mUserController.getCurrentUserId() == userId;
+ boolean isProfile = UserManagerService.getInstance().isProfile(userId);
+ boolean isVisible = LocalServices.getService(UserManagerInternal.class)
+ .isUserVisible(userId);
+ return isVisible && !isForeground && !isProfile;
+ }
+
+ /**
* Information about a process that is currently marked as bad.
*/
static final class BadProcessInfo {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 67985efcd7bc..8df4e7702be8 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -102,6 +102,7 @@ import android.util.StatsEvent;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.Clock;
@@ -671,6 +672,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
Flags.streamlinedMiscBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_GNSS,
+ Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_GNSS,
+ Flags.streamlinedMiscBatteryStats());
+
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
Flags.streamlinedMiscBatteryStats());
mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
@@ -1097,18 +1104,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
final StatsPullAtomCallbackImpl pullAtomCallback = new StatsPullAtomCallbackImpl();
- statsManager.setPullAtomCallback(
- FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET,
- null, // use default PullAtomMetadata values
- DIRECT_EXECUTOR, pullAtomCallback);
- statsManager.setPullAtomCallback(
- FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL,
- null, // use default PullAtomMetadata values
- DIRECT_EXECUTOR, pullAtomCallback);
- statsManager.setPullAtomCallback(
- FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
- null, // use default PullAtomMetadata values
- DIRECT_EXECUTOR, pullAtomCallback);
+ if (!Flags.disableCompositeBatteryUsageStatsAtoms()) {
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR, pullAtomCallback);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR, pullAtomCallback);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR, pullAtomCallback);
+ }
if (Flags.addBatteryUsageStatsSliceAtom()) {
statsManager.setPullAtomCallback(
FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
@@ -1125,6 +1134,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final BatteryUsageStats bus;
switch (atomTag) {
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
+ if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
+ return StatsManager.PULL_SKIP;
+ }
+
@SuppressLint("MissingPermission")
final double minConsumedPowerThreshold =
DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1141,6 +1154,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
break;
}
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
+ if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
+ return StatsManager.PULL_SKIP;
+ }
+
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
.setMaxStatsAgeMs(0)
@@ -1152,6 +1169,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
+ if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
+ return StatsManager.PULL_SKIP;
+ }
+
final long sessionStart =
getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
@@ -1191,7 +1212,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
.setMinConsumedPowerThreshold(minConsumedPowerThreshold)
.build();
bus = getBatteryUsageStats(List.of(query)).get(0);
- return StatsPerUidLogger.logStats(bus, data);
+ return new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
}
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -1204,7 +1225,35 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
- private static class StatsPerUidLogger {
+ public static class FrameworkStatsLogger {
+ /**
+ * Wrapper for the FrameworkStatsLog.buildStatsEvent method that makes it easier
+ * for mocking.
+ */
+ @VisibleForTesting
+ public StatsEvent buildStatsEvent(long sessionStartTs, long sessionEndTs,
+ long sessionDuration, int sessionDischargePercentage, long sessionDischargeDuration,
+ int uid, @BatteryConsumer.ProcessState int processState, long timeInStateMillis,
+ String powerComponentName, float totalConsumedPowerMah, float powerComponentMah,
+ long powerComponentDurationMillis) {
+ return FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ sessionStartTs,
+ sessionEndTs,
+ sessionDuration,
+ sessionDischargePercentage,
+ sessionDischargeDuration,
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerComponentMah,
+ powerComponentDurationMillis);
+ }
+ }
+
+ public static class StatsPerUidLogger {
private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000;
@@ -1224,7 +1273,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub
long dischargeDuration) {}
;
- static int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
+ private final FrameworkStatsLogger mFrameworkStatsLogger;
+
+ public StatsPerUidLogger(FrameworkStatsLogger frameworkStatsLogger) {
+ mFrameworkStatsLogger = frameworkStatsLogger;
+ }
+
+ /**
+ * Generates StatsEvents for the supplied battery usage stats and adds them to
+ * the supplied list.
+ */
+ @VisibleForTesting
+ public int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
final SessionInfo sessionInfo =
new SessionInfo(
bus.getStatsStartTimestamp(),
@@ -1340,7 +1400,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return StatsManager.PULL_SUCCESS;
}
- private static boolean addStatsForPredefinedComponent(
+ private boolean addStatsForPredefinedComponent(
List<StatsEvent> data,
SessionInfo sessionInfo,
int uid,
@@ -1380,7 +1440,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
powerComponentDurationMillis);
}
- private static boolean addStatsForCustomComponent(
+ private boolean addStatsForCustomComponent(
List<StatsEvent> data,
SessionInfo sessionInfo,
int uid,
@@ -1422,7 +1482,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
* Returns true on success and false if reached max atoms capacity and no more atoms should
* be added
*/
- private static boolean addStatsAtom(
+ private boolean addStatsAtom(
List<StatsEvent> data,
SessionInfo sessionInfo,
int uid,
@@ -1432,9 +1492,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
float totalConsumedPowerMah,
float powerComponentMah,
long powerComponentDurationMillis) {
- data.add(
- FrameworkStatsLog.buildStatsEvent(
- FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ data.add(mFrameworkStatsLogger.buildStatsEvent(
sessionInfo.startTs(),
sessionInfo.endTs(),
sessionInfo.duration(),
@@ -3214,6 +3272,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
.setMaxStatsAgeMs(0)
.includeProcessStateData()
.includePowerModels();
+ if (Flags.batteryUsageStatsByPowerAndScreenState()) {
+ builder.includeScreenStateData().includePowerStateData();
+ }
if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
builder.powerProfileModeledOnly();
}
@@ -3232,7 +3293,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
if (proto) {
batteryUsageStats.dumpToProto(fd);
} else {
- batteryUsageStats.dump(pw, "");
+ batteryUsageStats.dump(pw, " ");
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b058bd8eb3d6..504c54aefc44 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1708,6 +1708,11 @@ public class OomAdjuster {
// priority for this non-top split.
schedGroup = SCHED_GROUP_TOP_APP;
mAdjType = "resumed-split-screen-activity";
+ } else if ((flags
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0) {
+ // The recently used non-top visible freeform app.
+ schedGroup = SCHED_GROUP_TOP_APP;
+ mAdjType = "perceptible-freeform-activity";
}
foregroundActivities = true;
mHasVisibleActivities = true;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f6df60f736e8..e4c65bd2147d 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -269,7 +269,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
- new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
+ new GameManagerShellCommand(mPackageManager).exec(this, in, out, err, args, callback,
+ result);
}
@Override
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index ab57c4fe837e..d3b4312bf4a3 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -16,10 +16,13 @@
package com.android.server.app;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.IGameManagerService;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -47,7 +50,10 @@ public class GameManagerShellCommand extends ShellCommand {
private static final String UNSUPPORTED_MODE_NUM = String.valueOf(
GameManager.GAME_MODE_UNSUPPORTED);
- public GameManagerShellCommand() {
+ private PackageManager mPackageManager;
+
+ public GameManagerShellCommand(@NonNull PackageManager packageManager) {
+ mPackageManager = packageManager;
}
@Override
@@ -91,9 +97,29 @@ public class GameManagerShellCommand extends ShellCommand {
return -1;
}
+ private boolean isPackageGame(String packageName, int userId, PrintWriter pw) {
+ try {
+ final ApplicationInfo applicationInfo = mPackageManager
+ .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+ boolean isGame = applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
+ if (!isGame) {
+ pw.println("Package " + packageName + " is not of game type, to use the game "
+ + "mode commands, it must specify game category in the manifest as "
+ + "android:appCategory=\"game\"");
+ }
+ return isGame;
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Package " + packageName + " is not found for user " + userId);
+ return false;
+ }
+ }
+
private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
final String packageName = getNextArgRequired();
final int userId = ActivityManager.getCurrentUser();
+ if (!isPackageGame(packageName, userId, pw)) {
+ return -1;
+ }
final GameManagerService gameManagerService = (GameManagerService)
ServiceManager.getService(Context.GAME_SERVICE);
final String currentMode = gameModeIntToString(
@@ -110,12 +136,15 @@ public class GameManagerShellCommand extends ShellCommand {
private int runListGameModeConfigs(PrintWriter pw)
throws ServiceNotFoundException, RemoteException {
final String packageName = getNextArgRequired();
-
+ final int userId = ActivityManager.getCurrentUser();
+ if (!isPackageGame(packageName, userId, pw)) {
+ return -1;
+ }
final GameManagerService gameManagerService = (GameManagerService)
ServiceManager.getService(Context.GAME_SERVICE);
final String listStr = gameManagerService.getInterventionList(packageName,
- ActivityManager.getCurrentUser());
+ userId);
if (listStr == null) {
pw.println("No interventions found for " + packageName);
@@ -131,15 +160,17 @@ public class GameManagerShellCommand extends ShellCommand {
if (option != null && option.equals("--user")) {
userIdStr = getNextArgRequired();
}
-
final String gameMode = getNextArgRequired();
final String packageName = getNextArgRequired();
+ int userId = userIdStr != null ? Integer.parseInt(userIdStr)
+ : ActivityManager.getCurrentUser();
+ if (!isPackageGame(packageName, userId, pw)) {
+ return -1;
+ }
final IGameManagerService service = IGameManagerService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
boolean batteryModeSupported = false;
boolean perfModeSupported = false;
- int userId = userIdStr != null ? Integer.parseInt(userIdStr)
- : ActivityManager.getCurrentUser();
int[] modes = service.getAvailableGameModes(packageName, userId);
for (int mode : modes) {
if (mode == GameManager.GAME_MODE_PERFORMANCE) {
@@ -262,6 +293,9 @@ public class GameManagerShellCommand extends ShellCommand {
int userId = userIdStr != null ? Integer.parseInt(userIdStr)
: ActivityManager.getCurrentUser();
+ if (!isPackageGame(packageName, userId, pw)) {
+ return -1;
+ }
final GameManagerService gameManagerService = (GameManagerService)
ServiceManager.getService(Context.GAME_SERVICE);
@@ -308,13 +342,14 @@ public class GameManagerShellCommand extends ShellCommand {
}
final String packageName = getNextArgRequired();
-
- final GameManagerService gameManagerService = (GameManagerService)
- ServiceManager.getService(Context.GAME_SERVICE);
-
int userId = userIdStr != null ? Integer.parseInt(userIdStr)
: ActivityManager.getCurrentUser();
+ if (!isPackageGame(packageName, userId, pw)) {
+ return -1;
+ }
+ final GameManagerService gameManagerService = (GameManagerService)
+ ServiceManager.getService(Context.GAME_SERVICE);
if (gameMode == null) {
gameManagerService.resetGameModeConfigOverride(packageName, userId, -1);
return 0;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fab0a56af2a8..fe3bbb094284 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -739,6 +739,8 @@ public class AudioService extends IAudioService.Stub
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
+ private final Executor mAudioServerLifecycleExecutor;
+
private IMediaProjectionManager mProjectionService; // to validate projection token
/** Interface for UserManagerService. */
@@ -1059,7 +1061,8 @@ public class AudioService extends IAudioService.Stub
audioserverPermissions() ?
initializeAudioServerPermissionProvider(
context, audioPolicyFacade, audioserverLifecycleExecutor) :
- null
+ null,
+ audioserverLifecycleExecutor
);
}
@@ -1145,13 +1148,16 @@ public class AudioService extends IAudioService.Stub
* {@link AudioSystemThread} is created as the messaging thread instead.
* @param appOps {@link AppOpsManager} system service
* @param enforcer Used for permission enforcing
+ * @param permissionProvider Used to push permissions to audioserver
+ * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
@Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
- /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
+ /* @NonNull */ AudioServerPermissionProvider permissionProvider,
+ Executor audioserverLifecycleExecutor) {
super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
@@ -1159,6 +1165,7 @@ public class AudioService extends IAudioService.Stub
mAppOps = appOps;
mPermissionProvider = permissionProvider;
+ mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
mAudioSystem = audioSystem;
mSystemServer = systemServer;
@@ -1170,6 +1177,34 @@ public class AudioService extends IAudioService.Stub
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
+ // Listen to permission invalidations for the PermissionProvider
+ if (audioserverPermissions()) {
+ final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
+ mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ new Runnable() {
+ // Roughly chosen to be long enough to suppress the autocork behavior
+ // of the permission cache (50ms), and longer than the task could reasonably
+ // take, even with many packages and users, while not introducing visible
+ // permission leaks - since the app needs to restart, and trigger an action
+ // which requires permissions from audioserver before this delay.
+ // For RECORD_AUDIO, we are additionally protected by appops.
+ final long UPDATE_DELAY_MS = 110;
+ final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
+ @Override
+ public void run() {
+ var currentTime = SystemClock.uptimeMillis();
+ if (currentTime > scheduledUpdateTimestamp.get()) {
+ scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
+ broadcastHandler.postAtTime( () ->
+ mAudioServerLifecycleExecutor.execute(mPermissionProvider
+ ::onPermissionStateChanged),
+ currentTime + UPDATE_DELAY_MS
+ );
+ }
+ }
+ });
+ }
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -9918,9 +9953,9 @@ public class AudioService extends IAudioService.Stub
int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
- if (stream != null && !mAvrcpAbsVolSupported) {
+ if (!mAvrcpAbsVolSupported) {
mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
- "", /*enabled*/false, AudioSystem.DEVICE_NONE);
+ "", /*enabled*/false, AudioSystem.STREAM_DEFAULT);
return null;
}
// For A2DP and AVRCP we need to set the driving stream based on the
@@ -11974,29 +12009,6 @@ public class AudioService extends IAudioService.Stub
provider.onServiceStart(audioPolicy.getPermissionController());
});
- // Set up event listeners
- // Must be kept in sync with PermissionManager
- Runnable cacheSysPropHandler = new Runnable() {
- private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
- private AtomicLong mNonce = new AtomicLong();
- @Override
- public void run() {
- if (mHandle.get() == null) {
- // Cache the handle
- mHandle.compareAndSet(null, SystemProperties.find(
- PermissionManager.CACHE_KEY_PACKAGE_INFO));
- }
- long nonce;
- SystemProperties.Handle ref;
- if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
- mNonce.getAndSet(nonce) != nonce) {
- audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
- }
- }
- };
-
- SystemProperties.addChangeCallback(cacheSysPropHandler);
-
IntentFilter packageUpdateFilter = new IntentFilter();
packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7f4bc74bd59e..d083c68c4c2c 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,6 +748,10 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
return AudioSystem.setMasterMute(mute);
}
+ public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+ AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+ }
+
/**
* Part of AudioService dump
* @param pw
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 8e8a0378c111..8ec835bae0bf 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -41,6 +42,7 @@ import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -54,8 +56,12 @@ import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.camera2.CameraManager;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.security.keymint.HardwareAuthenticatorType;
import android.net.Uri;
import android.os.Binder;
@@ -234,6 +240,8 @@ public class BiometricService extends SystemService {
private static final boolean DEFAULT_APP_ENABLED = true;
private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false;
private static final boolean DEFAULT_MANDATORY_BIOMETRICS_STATUS = false;
+ private static final boolean DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS =
+ true;
// Some devices that shipped before S already have face-specific settings. Instead of
// migrating, which is complicated, let's just keep using the existing settings.
@@ -256,6 +264,8 @@ public class BiometricService extends SystemService {
Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
private final Uri MANDATORY_BIOMETRICS_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS);
+ private final Uri MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = Settings.Secure.getUriFor(
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED);
private final ContentResolver mContentResolver;
private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
@@ -264,6 +274,12 @@ public class BiometricService extends SystemService {
private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>();
+ private final Map<Integer, Boolean> mMandatoryBiometricsRequirementsSatisfied =
+ new HashMap<>();
+ private final Map<Integer, Boolean> mFingerprintEnrolledForUser =
+ new HashMap<>();
+ private final Map<Integer, Boolean> mFaceEnrolledForUser =
+ new HashMap<>();
/**
* Creates a content observer.
@@ -288,7 +304,13 @@ public class BiometricService extends SystemService {
mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser(
mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0);
+ mMandatoryBiometricsRequirementsSatisfied.put(context.getUserId(),
+ Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
+ context.getUserId()) != 0);
+ addBiometricListenersForMandatoryBiometrics(context);
updateContentObserver();
}
@@ -322,6 +344,10 @@ public class BiometricService extends SystemService {
false /* notifyForDescendants */,
this /* observer */,
UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
}
@Override
@@ -370,6 +396,13 @@ public class BiometricService extends SystemService {
Settings.Secure.MANDATORY_BIOMETRICS,
DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */,
userId) != 0);
+ } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
+ mMandatoryBiometricsRequirementsSatisfied.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS
+ ? 1 : 0 /* default */,
+ userId) != 0);
}
}
@@ -411,9 +444,15 @@ public class BiometricService extends SystemService {
}
}
- public boolean getMandatoryBiometricsEnabledForUser(int userId) {
+ public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
return mMandatoryBiometricsEnabled.getOrDefault(userId,
- DEFAULT_MANDATORY_BIOMETRICS_STATUS);
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS)
+ && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
+ && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED)
+ && getEnabledForApps(userId)
+ && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+ || mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
}
void notifyEnabledOnKeyguardCallbacks(int userId) {
@@ -424,6 +463,79 @@ public class BiometricService extends SystemService {
userId);
}
}
+
+ private void addBiometricListenersForMandatoryBiometrics(Context context) {
+ final FingerprintManager fingerprintManager = context.getSystemService(
+ FingerprintManager.class);
+ final FaceManager faceManager = context.getSystemService(FaceManager.class);
+ if (fingerprintManager != null) {
+ fingerprintManager.addAuthenticatorsRegisteredCallback(
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> list) {
+ if (list == null || list.isEmpty()) {
+ Slog.d(TAG, "No fingerprint authenticators registered.");
+ return;
+ }
+ final FingerprintSensorPropertiesInternal
+ fingerprintSensorProperties = list.get(0);
+ if (fingerprintSensorProperties.sensorStrength
+ == STRENGTH_STRONG) {
+ fingerprintManager.registerBiometricStateListener(
+ new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(
+ int userId,
+ int sensorId,
+ boolean hasEnrollments
+ ) {
+ if (sensorId == fingerprintSensorProperties
+ .sensorId) {
+ mFingerprintEnrolledForUser.put(userId,
+ hasEnrollments);
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ if (faceManager != null) {
+ faceManager.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> list) {
+ if (list == null || list.isEmpty()) {
+ Slog.d(TAG, "No face authenticators registered.");
+ return;
+ }
+ final FaceSensorPropertiesInternal
+ faceSensorPropertiesInternal = list.get(0);
+ if (faceSensorPropertiesInternal.sensorStrength
+ == STRENGTH_STRONG) {
+ faceManager.registerBiometricStateListener(
+ new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(
+ int userId,
+ int sensorId,
+ boolean hasEnrollments
+ ) {
+ if (sensorId
+ == faceSensorPropertiesInternal
+ .sensorId) {
+ mFaceEnrolledForUser.put(userId,
+ hasEnrollments);
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ }
}
final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index b9e6563a7d6a..0bd22f3da67f 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -112,8 +112,8 @@ class PreAuthInfo {
== BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
if (dropCredentialFallback(promptInfo.getAuthenticators(),
- settingObserver.getMandatoryBiometricsEnabledForUser(userId),
- trustManager)) {
+ settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ userId), trustManager)) {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setNegativeButtonText(context.getString(R.string.cancel));
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 73aa14ba016f..78f71877afed 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -684,7 +684,8 @@ public class ClipboardService extends SystemService {
if (clipboard == null) {
return null;
}
- showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
+ showAccessNotificationLocked(
+ pkg, intendingUid, intendingUserId, clipboard, deviceId);
notifyTextClassifierLocked(clipboard, pkg, intendingUid);
if (clipboard.primaryClip != null) {
scheduleAutoClear(userId, intendingUid, intendingDeviceId);
@@ -1438,7 +1439,7 @@ public class ClipboardService extends SystemService {
*/
@GuardedBy("mLock")
private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
- Clipboard clipboard) {
+ Clipboard clipboard, int accessDeviceId) {
if (clipboard.primaryClip == null) {
return;
}
@@ -1477,7 +1478,7 @@ public class ClipboardService extends SystemService {
return;
}
- final ArraySet<Context> toastContexts = getToastContexts(clipboard);
+ final ArraySet<Context> toastContexts = getToastContexts(clipboard, accessDeviceId);
Binder.withCleanCallingIdentity(() -> {
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
@@ -1516,40 +1517,55 @@ public class ClipboardService extends SystemService {
* If the clipboard is for a VirtualDevice, we attempt to return the single DisplayContext for
* the focused VirtualDisplay for that device, but might need to return the contexts for
* multiple displays if the VirtualDevice has several but none of them were focused.
+ *
+ * If the clipboard is NOT for a VirtualDevice, but it's being accessed from a VirtualDevice,
+ * this means that the clipboard is shared between the default and that device. In this case we
+ * need to show a toast in both places.
*/
- private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException {
+ private ArraySet<Context> getToastContexts(Clipboard clipboard, int accessDeviceId)
+ throws IllegalStateException {
ArraySet<Context> contexts = new ArraySet<>();
+ if (clipboard.deviceId == DEVICE_ID_DEFAULT || accessDeviceId == DEVICE_ID_DEFAULT) {
+ // Always show the toast on the default display when the default clipboard is accessed -
+ // also when the clipboard is shared with a virtual device and accessed from there.
+ // Same when any clipboard is accessed from the default device.
+ contexts.add(getContext());
+ }
- if (mVdmInternal != null && clipboard.deviceId != DEVICE_ID_DEFAULT) {
- DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+ if ((accessDeviceId == DEVICE_ID_DEFAULT && clipboard.deviceId == DEVICE_ID_DEFAULT)
+ || mVdmInternal == null) {
+ // No virtual devices involved.
+ return contexts;
+ }
- int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
- ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(clipboard.deviceId);
+ // At this point the clipboard is either accessed from a virtual device, or it is a virtual
+ // device clipboard, so show a toast on the relevant virtual display(s).
+ DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+ ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(accessDeviceId);
+ int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
- if (displayIds.contains(topFocusedDisplayId)) {
- Display display = displayManager.getDisplay(topFocusedDisplayId);
- if (display != null) {
- contexts.add(getContext().createDisplayContext(display));
- return contexts;
- }
+ if (displayIds.contains(topFocusedDisplayId)) {
+ Display display = displayManager.getDisplay(topFocusedDisplayId);
+ if (display != null) {
+ contexts.add(getContext().createDisplayContext(display));
+ return contexts;
}
+ }
- for (int i = 0; i < displayIds.size(); i++) {
- Display display = displayManager.getDisplay(displayIds.valueAt(i));
- if (display != null) {
- contexts.add(getContext().createDisplayContext(display));
- }
- }
- if (!contexts.isEmpty()) {
- return contexts;
+ for (int i = 0; i < displayIds.size(); i++) {
+ Display display = displayManager.getDisplay(displayIds.valueAt(i));
+ if (display != null) {
+ contexts.add(getContext().createDisplayContext(display));
}
+ }
+ if (contexts.isEmpty()) {
Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice "
- + clipboard.deviceId);
+ + accessDeviceId);
// Since we couldn't find any VirtualDisplays to use at all, just fall through to using
// the default display below.
+ contexts.add(getContext());
}
- contexts.add(getContext());
return contexts;
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 22b85d45d15e..a9fe8cb01b3a 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -248,6 +248,25 @@ public class PlatformCompat extends IPlatformCompat.Stub {
return enabled;
}
+ /**
+ * Internal version of {@link #isChangeEnabledByUid(long, int)}.
+ *
+ * <p>Does not perform costly permission check and logging.
+ */
+ public boolean isChangeEnabledByUidInternalNoLogging(long changeId, int uid) {
+ String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return mCompatConfig.defaultChangeIdValue(changeId);
+ }
+ boolean enabled = true;
+ final int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ final var appInfo = getApplicationInfo(packageName, userId);
+ enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo);
+ }
+ return enabled;
+ }
+
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
index 133c79f81bb5..8e725465ddd6 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -24,6 +24,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.net.ConnectivityModuleConnector;
+import android.sysprop.CrashRecoveryProperties;
import android.text.TextUtils;
import android.util.Slog;
@@ -35,7 +36,7 @@ import java.util.List;
/**
* Provides helper methods for the CrashRecovery APEX
- *
+ * TODO: b/354112511 Add tests for this class when it is finalized.
* @hide
*/
public final class CrashRecoveryHelper {
@@ -76,11 +77,13 @@ public final class CrashRecoveryHelper {
}
/**
- * Register health listeners for explicit package failures.
- * Currently only registering for Connectivity Module health.
- * @hide
+ * Register health listeners for Connectivity packages health.
+ *
+ * TODO: b/354112511 Have an internal method to trigger a rollback by reporting high severity errors,
+ * and rely on ActivityManager to inform the watchdog of severe network stack crashes
+ * instead of having this listener in parallel.
*/
- public void registerConnectivityModuleHealthListener(@NonNull int failureReason) {
+ public void registerConnectivityModuleHealthListener() {
// register listener for ConnectivityModule
mConnectivityModuleConnector.registerHealthListener(
packageName -> {
@@ -90,7 +93,7 @@ public final class CrashRecoveryHelper {
return;
}
final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
- PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason);
+ PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
});
}
@@ -126,4 +129,21 @@ public final class CrashRecoveryHelper {
return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
}
}
+
+ /**
+ * Check if we're currently attempting to reboot for a factory reset. This method must
+ * return true if RescueParty tries to reboot early during a boot loop, since the device
+ * will not be fully booted at this time.
+ */
+ public static boolean isRecoveryTriggeredReboot() {
+ return isFactoryResetPropertySet() || isRebootPropertySet();
+ }
+
+ static boolean isFactoryResetPropertySet() {
+ return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
+ }
+
+ static boolean isRebootPropertySet() {
+ return CrashRecoveryProperties.attemptingReboot().orElse(false);
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7a055d1d7e5c..ed6ed60a6806 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -99,6 +99,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.function.Function;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -1192,6 +1193,18 @@ public class DisplayDeviceConfig {
*/
public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio) {
Spline sdrToHdrSpline = mHbmData != null ? mHbmData.sdrToHdrRatioSpline : null;
+ return getHdrBrightnessFromSdr(brightness, maxDesiredHdrSdrRatio, sdrToHdrSpline);
+ }
+
+ /**
+ * Calculate the HDR brightness for the specified SDR brightenss, restricted by the
+ * maxDesiredHdrSdrRatio (the ratio between the HDR luminance and SDR luminance) and specific
+ * sdrToHdrSpline
+ *
+ * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists.
+ */
+ public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio,
+ @Nullable Spline sdrToHdrSpline) {
if (sdrToHdrSpline == null) {
return PowerManager.BRIGHTNESS_INVALID;
}
@@ -1786,15 +1799,17 @@ public class DisplayDeviceConfig {
loadThermalThrottlingConfig(config);
loadPowerThrottlingConfigData(config);
// Backlight and evenDimmer data should be loaded for HbmData
- mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config, (hbm) -> {
+ Function<HighBrightnessMode, Float> transitionPointProvider = (hbm) -> {
float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue();
if (transitionPointBacklightScale >= mBacklightMaximum) {
throw new IllegalArgumentException("HBM transition point invalid. "
- + mHbmData.transitionPoint + " is not less than "
+ + transitionPointBacklightScale + " is not less than "
+ mBacklightMaximum);
}
return getBrightnessFromBacklight(transitionPointBacklightScale);
- });
+ };
+ mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config,
+ transitionPointProvider);
if (mHbmData.isHighBrightnessModeEnabled && mHbmData.refreshRateLimit != null) {
// TODO(b/331650248): cleanup, DMD can use mHbmData.refreshRateLimit
mRefreshRateLimitations.add(new RefreshRateLimitation(
@@ -1818,7 +1833,7 @@ public class DisplayDeviceConfig {
loadRefreshRateSetting(config);
loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
loadUsiVersion(config);
- mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
+ mHdrBrightnessData = HdrBrightnessData.loadConfig(config, transitionPointProvider);
loadBrightnessCapForWearBedtimeMode(config);
loadIdleScreenRefreshRateTimeoutConfigs(config);
mVrrSupportEnabled = config.getSupportsVrr();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b3a6c1c1e20a..2cec869c290e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3849,9 +3849,10 @@ public final class DisplayManagerService extends SystemService {
// Ignore redundant events. Further optimization is possible by merging adjacent events.
Pair<Integer, Integer> last = mDisplayEvents.get(mDisplayEvents.size() - 1);
if (last.first == displayId && last.second == event) {
- Slog.d(TAG,
- "Ignore redundant display event " + displayId + "/" + event + " to "
- + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid);
+ if (DEBUG) {
+ Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event + " to "
+ + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid);
+ }
return;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6992580e4df8..1177be212222 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -505,14 +505,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mClock = mInjector.getClock();
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+ mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
+ DisplayDeviceInfo displayDeviceInfo = mDisplayDevice.getDisplayDeviceInfoLocked();
mSensorManager = sensorManager;
mHandler = new DisplayControllerHandler(handler.getLooper());
- mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceConfig();
+ mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
mIsEnabled = logicalDisplay.isEnabledLocked();
mIsInTransition = logicalDisplay.isInTransitionLocked();
- mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
+ mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
@@ -521,7 +522,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mTag = TAG + "[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
- mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+
mUniqueDisplayId = mDisplayDevice.getUniqueId();
mDisplayStatsId = mUniqueDisplayId.hashCode();
mPhysicalDisplayName = mDisplayDevice.getNameLocked();
@@ -569,8 +570,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
- mDisplayDevice.getDisplayTokenLocked(),
- mDisplayDevice.getDisplayDeviceInfoLocked());
+ displayToken, displayDeviceInfo);
mDisplayBrightnessController =
new DisplayBrightnessController(context, null,
@@ -584,8 +584,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
logicalDisplay.getPowerThrottlingDataIdLocked(),
- mDisplayDeviceConfig,
- mDisplayId), mContext, flags, mSensorManager);
+ mDisplayDeviceConfig, displayDeviceInfo.width, displayDeviceInfo.height,
+ displayToken, mDisplayId), mContext, flags, mSensorManager);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
mAutomaticBrightnessStrategy =
@@ -893,7 +893,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessClamperController.onDisplayChanged(
new BrightnessClamperController.DisplayDeviceData(uniqueId,
thermalBrightnessThrottlingDataId, powerThrottlingDataId,
- config, mDisplayId));
+ config, info.width, info.height, token, mDisplayId));
if (changed) {
updatePowerState();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 88d2c007cf37..9324fc1c4e06 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -28,13 +28,16 @@ import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.IBinder;
import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import android.util.Spline;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
@@ -65,6 +68,11 @@ public class BrightnessClamperController {
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
private final List<BrightnessStateModifier> mModifiers;
+
+ private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>();
+ private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>();
+ private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
+
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
@@ -110,7 +118,16 @@ public class BrightnessClamperController {
mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
context);
mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
- data.mDisplayDeviceConfig);
+ data);
+
+ mModifiers.forEach(m -> {
+ if (m instanceof DisplayDeviceDataListener l) {
+ mDisplayDeviceDataListeners.add(l);
+ }
+ if (m instanceof StatefulModifier s) {
+ mStatefulModifiers.add(s);
+ }
+ });
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
@@ -123,6 +140,7 @@ public class BrightnessClamperController {
public void onDisplayChanged(DisplayDeviceData data) {
mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+ mDisplayDeviceDataListeners.forEach(l -> l.onDisplayChanged(data));
adjustLightSensorSubscription();
}
@@ -234,14 +252,28 @@ public class BrightnessClamperController {
customAnimationRate = minClamper.getCustomAnimationRate();
}
+ ModifiersAggregatedState newAggregatedState = new ModifiersAggregatedState();
+ mStatefulModifiers.forEach((clamper) -> clamper.applyStateChange(newAggregatedState));
+
if (mBrightnessCap != brightnessCap
|| mClamperType != clamperType
- || mCustomAnimationRate != customAnimationRate) {
+ || mCustomAnimationRate != customAnimationRate
+ || needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
mBrightnessCap = brightnessCap;
mClamperType = clamperType;
mCustomAnimationRate = customAnimationRate;
mClamperChangeListenerExternal.onChanged();
}
+ mModifiersAggregatedState = newAggregatedState;
+ }
+
+ private boolean needToNotifyExternalListener(ModifiersAggregatedState state1,
+ ModifiersAggregatedState state2) {
+ return !BrightnessSynchronizer.floatEquals(state1.mMaxDesiredHdrRatio,
+ state2.mMaxDesiredHdrRatio)
+ || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness,
+ state2.mMaxHdrBrightness)
+ || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline;
}
private void start() {
@@ -295,17 +327,16 @@ public class BrightnessClamperController {
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, ClamperChangeListener listener,
- DisplayDeviceConfig displayDeviceConfig) {
+ DisplayDeviceData data) {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
- if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null
- && displayDeviceConfig.isEvenDimmerAvailable()) {
+ if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
- displayDeviceConfig));
+ data.mDisplayDeviceConfig));
}
if (flags.useNewHdrBrightnessModifier()) {
- modifiers.add(new HdrBrightnessModifier());
+ modifiers.add(new HdrBrightnessModifier(handler, listener, data));
}
return modifiers;
}
@@ -319,7 +350,14 @@ public class BrightnessClamperController {
}
/**
- * Config Data for clampers
+ * Modifier should implement this interface in order to receive display change updates
+ */
+ interface DisplayDeviceDataListener {
+ void onDisplayChanged(DisplayDeviceData displayData);
+ }
+
+ /**
+ * Config Data for clampers/modifiers
*/
public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
BrightnessPowerClamper.PowerData,
@@ -331,23 +369,34 @@ public class BrightnessClamperController {
@NonNull
private final String mPowerThrottlingDataId;
@NonNull
- private final DisplayDeviceConfig mDisplayDeviceConfig;
+ final DisplayDeviceConfig mDisplayDeviceConfig;
+
+ final int mWidth;
- private final int mDisplayId;
+ final int mHeight;
+
+ final IBinder mDisplayToken;
+
+ final int mDisplayId;
public DisplayDeviceData(@NonNull String uniqueDisplayId,
@NonNull String thermalThrottlingDataId,
@NonNull String powerThrottlingDataId,
@NonNull DisplayDeviceConfig displayDeviceConfig,
+ int width,
+ int height,
+ IBinder displayToken,
int displayId) {
mUniqueDisplayId = uniqueDisplayId;
mThermalThrottlingDataId = thermalThrottlingDataId;
mPowerThrottlingDataId = powerThrottlingDataId;
mDisplayDeviceConfig = displayDeviceConfig;
+ mWidth = width;
+ mHeight = height;
+ mDisplayToken = displayToken;
mDisplayId = displayId;
}
-
@NonNull
@Override
public String getUniqueDisplayId() {
@@ -406,4 +455,24 @@ public class BrightnessClamperController {
return mDisplayId;
}
}
+
+ /**
+ * Stateful modifier should implement this interface and modify aggregatedState.
+ * AggregatedState is used by Controller to determine if updatePowerState call is needed
+ * to correctly adjust brightness
+ */
+ interface StatefulModifier {
+ void applyStateChange(ModifiersAggregatedState aggregatedState);
+ }
+
+ /**
+ * StatefulModifiers contribute to AggregatedState, that is used to decide if brightness
+ * adjustement is needed
+ */
+ public static class ModifiersAggregatedState {
+ float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO;
+ float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX;
+ @Nullable
+ Spline mSdrHdrRatioSpline = null;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
index a829866ae6fc..5e44cc357b28 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
@@ -16,36 +16,325 @@
package com.android.server.display.brightness.clamper;
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+import static com.android.server.display.brightness.clamper.LightSensorController.INVALID_LUX;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.view.SurfaceControlHdrLayerInfoListener;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
+import java.util.Map;
+
+public class HdrBrightnessModifier implements BrightnessStateModifier,
+ BrightnessClamperController.DisplayDeviceDataListener,
+ BrightnessClamperController.StatefulModifier {
+
+ static final float DEFAULT_MAX_HDR_SDR_RATIO = 1.0f;
+ private static final float DEFAULT_HDR_LAYER_SIZE = -1.0f;
+
+ private final SurfaceControlHdrLayerInfoListener mHdrListener =
+ new SurfaceControlHdrLayerInfoListener() {
+ @Override
+ public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW,
+ int maxH, int flags, float maxDesiredHdrSdrRatio) {
+ boolean hdrLayerPresent = numberOfHdrLayers > 0;
+ mHandler.post(() -> HdrBrightnessModifier.this.onHdrInfoChanged(
+ hdrLayerPresent ? (float) (maxW * maxH) : DEFAULT_HDR_LAYER_SIZE,
+ hdrLayerPresent ? maxDesiredHdrSdrRatio : DEFAULT_MAX_HDR_SDR_RATIO));
+ }
+ };
+
+ private final Handler mHandler;
+ private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener;
+ private final Injector mInjector;
+ private final Runnable mDebouncer;
+
+ private IBinder mRegisteredDisplayToken;
+
+ private DisplayDeviceConfig mDisplayDeviceConfig;
+ @Nullable
+ private HdrBrightnessData mHdrBrightnessData;
+ private float mScreenSize;
-public class HdrBrightnessModifier implements BrightnessStateModifier {
+ private float mMaxDesiredHdrRatio = DEFAULT_MAX_HDR_SDR_RATIO;
+ private float mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE;
+
+ private float mAmbientLux = INVALID_LUX;
+
+ private Mode mMode = Mode.NO_HDR;
+ // The maximum brightness allowed for current lux
+ private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+ private float mPendingMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+ // brightness change speed, in units per seconds. Applied only on ambient lux changes
+ private float mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+ private float mPendingTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+
+ HdrBrightnessModifier(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData displayData) {
+ this(new Handler(handler.getLooper()), clamperChangeListener, new Injector(), displayData);
+ }
+
+ @VisibleForTesting
+ HdrBrightnessModifier(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ Injector injector,
+ BrightnessClamperController.DisplayDeviceData displayData) {
+ mHandler = handler;
+ mClamperChangeListener = clamperChangeListener;
+ mInjector = injector;
+ mDebouncer = () -> {
+ mTransitionRate = mPendingTransitionRate;
+ mMaxBrightness = mPendingMaxBrightness;
+ mClamperChangeListener.onChanged();
+ };
+ onDisplayChanged(displayData);
+ }
+
+ // Called in DisplayControllerHandler
@Override
public void apply(DisplayManagerInternal.DisplayPowerRequest request,
DisplayBrightnessState.Builder stateBuilder) {
- // noop
+ if (mHdrBrightnessData == null) { // no hdr data
+ return;
+ }
+ if (mMode == Mode.NO_HDR) {
+ return;
+ }
+ float hdrBrightness = mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+ stateBuilder.getBrightness(), mMaxDesiredHdrRatio,
+ mHdrBrightnessData.sdrToHdrRatioSpline);
+ float maxBrightness = getMaxBrightness(mMode, mMaxBrightness, mHdrBrightnessData);
+ hdrBrightness = Math.min(hdrBrightness, maxBrightness);
+
+ stateBuilder.setHdrBrightness(hdrBrightness);
+ stateBuilder.setCustomAnimationRate(mTransitionRate);
+ // transition rate applied, reset
+ mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
}
@Override
- public void dump(PrintWriter printWriter) {
- // noop
+ public void dump(PrintWriter pw) {
+ pw.println("HdrBrightnessModifier:");
+ pw.println(" mHdrBrightnessData=" + mHdrBrightnessData);
+ pw.println(" mScreenSize=" + mScreenSize);
+ pw.println(" mMaxDesiredHdrRatio=" + mMaxDesiredHdrRatio);
+ pw.println(" mHdrLayerSize=" + mHdrLayerSize);
+ pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mMode=" + mMode);
+ pw.println(" mMaxBrightness=" + mMaxBrightness);
+ pw.println(" mPendingMaxBrightness=" + mPendingMaxBrightness);
+ pw.println(" mTransitionRate=" + mTransitionRate);
+ pw.println(" mPendingTransitionRate=" + mPendingTransitionRate);
+ pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
}
+ // Called in DisplayControllerHandler
@Override
public void stop() {
- // noop
+ unregisterHdrListener();
+ mHandler.removeCallbacksAndMessages(null);
}
+ // Called in DisplayControllerHandler
@Override
public boolean shouldListenToLightSensor() {
- return false;
+ return hasBrightnessLimits();
}
+ // Called in DisplayControllerHandler
@Override
public void setAmbientLux(float lux) {
- // noop
+ mAmbientLux = lux;
+ if (!hasBrightnessLimits()) {
+ return;
+ }
+ float desiredMaxBrightness = findBrightnessLimit(mHdrBrightnessData, lux);
+ if (mMode == Mode.NO_HDR) {
+ mMaxBrightness = desiredMaxBrightness;
+ } else {
+ scheduleMaxBrightnessUpdate(desiredMaxBrightness, mHdrBrightnessData);
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData displayData) {
+ mHandler.post(() -> onDisplayChanged(displayData.mDisplayToken, displayData.mWidth,
+ displayData.mHeight, displayData.mDisplayDeviceConfig));
+ }
+
+ // Called in DisplayControllerHandler, when any modifier state changes
+ @Override
+ public void applyStateChange(
+ BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+ if (mMode != Mode.NO_HDR && mHdrBrightnessData != null) {
+ aggregatedState.mMaxDesiredHdrRatio = mMaxDesiredHdrRatio;
+ aggregatedState.mSdrHdrRatioSpline = mHdrBrightnessData.sdrToHdrRatioSpline;
+ aggregatedState.mMaxHdrBrightness = getMaxBrightness(
+ mMode, mMaxBrightness, mHdrBrightnessData);
+ }
+ }
+
+ private boolean hasBrightnessLimits() {
+ return mHdrBrightnessData != null && !mHdrBrightnessData.maxBrightnessLimits.isEmpty();
+ }
+
+ private void scheduleMaxBrightnessUpdate(float desiredMaxBrightness, HdrBrightnessData data) {
+ if (mMaxBrightness == desiredMaxBrightness) {
+ mPendingMaxBrightness = mMaxBrightness;
+ mPendingTransitionRate = -1f;
+ mTransitionRate = -1f;
+ mHandler.removeCallbacks(mDebouncer);
+ } else if (mPendingMaxBrightness != desiredMaxBrightness) {
+ mPendingMaxBrightness = desiredMaxBrightness;
+ long debounceTime;
+ if (mPendingMaxBrightness > mMaxBrightness) {
+ debounceTime = data.brightnessIncreaseDebounceMillis;
+ mPendingTransitionRate = data.screenBrightnessRampIncrease;
+ } else {
+ debounceTime = data.brightnessDecreaseDebounceMillis;
+ mPendingTransitionRate = data.screenBrightnessRampDecrease;
+ }
+
+ mHandler.removeCallbacks(mDebouncer);
+ mHandler.postDelayed(mDebouncer, debounceTime);
+ }
+ // do nothing if expectedMaxBrightness == mDesiredMaxBrightness
+ // && expectedMaxBrightness != mMaxBrightness
+ }
+
+ // Called in DisplayControllerHandler
+ private void onDisplayChanged(IBinder displayToken, int width, int height,
+ DisplayDeviceConfig config) {
+ mDisplayDeviceConfig = config;
+ mScreenSize = (float) width * height;
+ HdrBrightnessData data = config.getHdrBrightnessData();
+ if (data == null) {
+ unregisterHdrListener();
+ } else {
+ registerHdrListener(displayToken);
+ }
+ recalculate(data, mMaxDesiredHdrRatio);
+ }
+
+ // Called in DisplayControllerHandler
+ private void recalculate(@Nullable HdrBrightnessData data, float maxDesiredHdrRatio) {
+ Mode newMode = recalculateMode(data);
+ // if HDR mode changed, notify changed
+ boolean needToNotifyChange = mMode != newMode;
+ // If HDR mode is active, we need to check if other HDR params are changed
+ if (mMode != HdrBrightnessModifier.Mode.NO_HDR) {
+ if (!BrightnessSynchronizer.floatEquals(mMaxDesiredHdrRatio, maxDesiredHdrRatio)
+ || data != mHdrBrightnessData) {
+ needToNotifyChange = true;
+ }
+ }
+
+ mMode = newMode;
+ mHdrBrightnessData = data;
+ mMaxDesiredHdrRatio = maxDesiredHdrRatio;
+
+ if (needToNotifyChange) {
+ // data or hdr layer changed, reset custom transition rate
+ mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+ mClamperChangeListener.onChanged();
+ }
+ }
+
+ // Called in DisplayControllerHandler
+ private Mode recalculateMode(@Nullable HdrBrightnessData data) {
+ // no config
+ if (data == null) {
+ return Mode.NO_HDR;
+ }
+ // HDR layer < minHdr % for Nbm
+ if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForNbm) {
+ return Mode.NO_HDR;
+ }
+ // HDR layer < minHdr % for Hbm, and HDR layer >= that minHdr % for Nbm
+ if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForHbm) {
+ return Mode.NBM_HDR;
+ }
+ // HDR layer > that minHdr % for Hbm
+ return Mode.HBM_HDR;
+ }
+
+ private float getMaxBrightness(Mode mode, float maxBrightness, HdrBrightnessData data) {
+ if (mode == Mode.NBM_HDR) {
+ return Math.min(data.hbmTransitionPoint, maxBrightness);
+ } else if (mode == Mode.HBM_HDR) {
+ return maxBrightness;
+ } else {
+ return PowerManager.BRIGHTNESS_MAX;
+ }
+ }
+
+ // Called in DisplayControllerHandler
+ private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
+ float foundAmbientBoundary = Float.MAX_VALUE;
+ float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+ for (Map.Entry<Float, Float> brightnessPoint :
+ data.maxBrightnessLimits.entrySet()) {
+ float ambientBoundary = brightnessPoint.getKey();
+ // find ambient lux upper boundary closest to current ambient lux
+ if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
+ foundMaxBrightness = brightnessPoint.getValue();
+ foundAmbientBoundary = ambientBoundary;
+ }
+ }
+ return foundMaxBrightness;
+ }
+
+ // Called in DisplayControllerHandler
+ private void onHdrInfoChanged(float hdrLayerSize, float maxDesiredHdrSdrRatio) {
+ mHdrLayerSize = hdrLayerSize;
+ recalculate(mHdrBrightnessData, maxDesiredHdrSdrRatio);
+ }
+
+ // Called in DisplayControllerHandler
+ private void registerHdrListener(IBinder displayToken) {
+ if (mRegisteredDisplayToken == displayToken) {
+ return;
+ }
+ unregisterHdrListener();
+ if (displayToken != null) {
+ mInjector.registerHdrListener(mHdrListener, displayToken);
+ mRegisteredDisplayToken = displayToken;
+ }
+ }
+
+ // Called in DisplayControllerHandler
+ private void unregisterHdrListener() {
+ if (mRegisteredDisplayToken != null) {
+ mInjector.unregisterHdrListener(mHdrListener, mRegisteredDisplayToken);
+ mRegisteredDisplayToken = null;
+ mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE;
+ }
+ }
+
+ private enum Mode {
+ NO_HDR, NBM_HDR, HBM_HDR
+ }
+
+ @SuppressLint("MissingPermission")
+ static class Injector {
+ void registerHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) {
+ listener.register(token);
+ }
+
+ void unregisterHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) {
+ listener.unregister(token);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index c9408077e5af..ef4a7984755d 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -19,6 +19,7 @@ package com.android.server.display.config;
import static com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
import android.annotation.Nullable;
+import android.os.PowerManager;
import android.util.Spline;
import com.android.internal.annotations.VisibleForTesting;
@@ -29,6 +30,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
/**
* Brightness config for HDR content
@@ -48,9 +50,9 @@ import java.util.Map;
* </point>
* </brightnessMap>
* <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>
- * <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
+ * <screenBrightnessRampIncrease>0.04</brightnessIncreaseDurationMillis>
* <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
- * <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ * <screenBrightnessRampDecrease>0.03</brightnessDecreaseDurationMillis>
* <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm>
* <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm>
* <allowInLowPowerMode>true</allowInLowPowerMode>
@@ -99,6 +101,11 @@ public class HdrBrightnessData {
public final float screenBrightnessRampDecrease;
/**
+ * Brightness level at which we transition from normal to high-brightness
+ */
+ public final float hbmTransitionPoint;
+
+ /**
* Min Hdr layer size to start hdr brightness boost up to high brightness mode transition point
*/
public final float minimumHdrPercentOfScreenForNbm;
@@ -123,6 +130,7 @@ public class HdrBrightnessData {
public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease,
+ float hbmTransitionPoint,
float minimumHdrPercentOfScreenForNbm, float minimumHdrPercentOfScreenForHbm,
boolean allowInLowPowerMode, @Nullable Spline sdrToHdrRatioSpline) {
this.maxBrightnessLimits = maxBrightnessLimits;
@@ -130,6 +138,7 @@ public class HdrBrightnessData {
this.screenBrightnessRampIncrease = screenBrightnessRampIncrease;
this.brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
this.screenBrightnessRampDecrease = screenBrightnessRampDecrease;
+ this.hbmTransitionPoint = hbmTransitionPoint;
this.minimumHdrPercentOfScreenForNbm = minimumHdrPercentOfScreenForNbm;
this.minimumHdrPercentOfScreenForHbm = minimumHdrPercentOfScreenForHbm;
this.allowInLowPowerMode = allowInLowPowerMode;
@@ -144,6 +153,7 @@ public class HdrBrightnessData {
+ ", mScreenBrightnessRampIncrease: " + screenBrightnessRampIncrease
+ ", mBrightnessDecreaseDebounceMillis: " + brightnessDecreaseDebounceMillis
+ ", mScreenBrightnessRampDecrease: " + screenBrightnessRampDecrease
+ + ", transitionPoint: " + hbmTransitionPoint
+ ", minimumHdrPercentOfScreenForNbm: " + minimumHdrPercentOfScreenForNbm
+ ", minimumHdrPercentOfScreenForHbm: " + minimumHdrPercentOfScreenForHbm
+ ", allowInLowPowerMode: " + allowInLowPowerMode
@@ -155,10 +165,12 @@ public class HdrBrightnessData {
* Loads HdrBrightnessData from DisplayConfiguration
*/
@Nullable
- public static HdrBrightnessData loadConfig(DisplayConfiguration config) {
+ public static HdrBrightnessData loadConfig(DisplayConfiguration config,
+ Function<HighBrightnessMode, Float> transitionPointProvider) {
+ HighBrightnessMode hbmConfig = config.getHighBrightnessMode();
HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig();
if (hdrConfig == null) {
- return getFallbackData(config.getHighBrightnessMode());
+ return getFallbackData(hbmConfig, transitionPointProvider);
}
List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint();
@@ -169,22 +181,38 @@ public class HdrBrightnessData {
float minHdrPercentForHbm = hdrConfig.getMinimumHdrPercentOfScreenForHbm() != null
? hdrConfig.getMinimumHdrPercentOfScreenForHbm().floatValue()
- : getFallbackHdrPercent(config.getHighBrightnessMode());
+ : getFallbackHdrPercent(hbmConfig);
float minHdrPercentForNbm = hdrConfig.getMinimumHdrPercentOfScreenForNbm() != null
? hdrConfig.getMinimumHdrPercentOfScreenForNbm().floatValue() : minHdrPercentForHbm;
+ if (minHdrPercentForNbm > minHdrPercentForHbm) {
+ throw new IllegalArgumentException(
+ "minHdrPercentForHbm should be >= minHdrPercentForNbm");
+ }
+
return new HdrBrightnessData(brightnessLimits,
hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
hdrConfig.getScreenBrightnessRampDecrease().floatValue(),
+ getTransitionPoint(hbmConfig, transitionPointProvider),
minHdrPercentForNbm, minHdrPercentForHbm, hdrConfig.getAllowInLowPowerMode(),
getSdrHdrRatioSpline(hdrConfig, config.getHighBrightnessMode()));
}
+ private static float getTransitionPoint(@Nullable HighBrightnessMode hbm,
+ Function<HighBrightnessMode, Float> transitionPointProvider) {
+ if (hbm == null) {
+ return PowerManager.BRIGHTNESS_MAX;
+ } else {
+ return transitionPointProvider.apply(hbm);
+ }
+ }
+
@Nullable
- private static HdrBrightnessData getFallbackData(HighBrightnessMode hbm) {
+ private static HdrBrightnessData getFallbackData(@Nullable HighBrightnessMode hbm,
+ Function<HighBrightnessMode, Float> transitionPointProvider) {
if (hbm == null) {
return null;
}
@@ -193,6 +221,7 @@ public class HdrBrightnessData {
return new HdrBrightnessData(Collections.emptyMap(),
0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
+ getTransitionPoint(hbm, transitionPointProvider),
fallbackPercent, fallbackPercent, false, fallbackSpline);
}
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
index 8bfc4a3ad77a..1437c8dba0cd 100644
--- a/services/core/java/com/android/server/display/config/SensorData.java
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -34,6 +34,8 @@ public class SensorData {
public static final String TEMPERATURE_TYPE_DISPLAY = "DISPLAY";
public static final String TEMPERATURE_TYPE_SKIN = "SKIN";
+ private static final SensorData UNSPECIFIED_SENSOR_DATA = new SensorData(
+ /* type= */null, /* name= */ null);
@Nullable
public final String type;
@@ -43,24 +45,14 @@ public class SensorData {
public final float maxRefreshRate;
public final List<SupportedModeData> supportedModes;
- @VisibleForTesting
- public SensorData() {
- this(/* type= */ null, /* name= */ null);
- }
-
- @VisibleForTesting
- public SensorData(String type, String name) {
- this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY);
- }
-
- @VisibleForTesting
- public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate) {
- this(type, name, minRefreshRate, maxRefreshRate, /* supportedModes= */ List.of());
+ private SensorData(@Nullable String type, @Nullable String name) {
+ this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY,
+ /* supportedModes= */ List.of());
}
@VisibleForTesting
- public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate,
- List<SupportedModeData> supportedModes) {
+ SensorData(@Nullable String type, @Nullable String name,
+ float minRefreshRate, float maxRefreshRate, List<SupportedModeData> supportedModes) {
this.type = type;
this.name = name;
this.minRefreshRate = minRefreshRate;
@@ -72,7 +64,7 @@ public class SensorData {
* @return True if the sensor matches both the specified name and type, or one if only one
* is specified (not-empty). Always returns false if both parameters are null or empty.
*/
- public boolean matches(String sensorName, String sensorType) {
+ public boolean matches(@Nullable String sensorName, @Nullable String sensorType) {
final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
return (isNameSpecified || isTypeSpecified)
@@ -120,7 +112,7 @@ public class SensorData {
if (sensorDetails != null) {
return loadSensorData(sensorDetails);
} else {
- return new SensorData();
+ return UNSPECIFIED_SENSOR_DATA;
}
}
@@ -130,13 +122,12 @@ public class SensorData {
@Nullable
public static SensorData loadProxSensorConfig(
DisplayManagerFlags flags, DisplayConfiguration config) {
- SensorData DEFAULT_SENSOR = new SensorData();
List<SensorDetails> sensorDetailsList = config.getProxSensor();
if (sensorDetailsList.isEmpty()) {
- return DEFAULT_SENSOR;
+ return UNSPECIFIED_SENSOR_DATA;
}
- SensorData selectedSensor = DEFAULT_SENSOR;
+ SensorData selectedSensor = UNSPECIFIED_SENSOR_DATA;
// Prioritize flagged sensors.
for (SensorDetails sensorDetails : sensorDetailsList) {
String flagStr = sensorDetails.getFeatureFlag();
@@ -148,7 +139,7 @@ public class SensorData {
}
// Check for normal un-flagged sensor if a flagged one wasn't found.
- if (DEFAULT_SENSOR == selectedSensor) {
+ if (UNSPECIFIED_SENSOR_DATA == selectedSensor) {
for (SensorDetails sensorDetails : sensorDetailsList) {
if (sensorDetails.getFeatureFlag() != null) {
continue;
@@ -159,7 +150,7 @@ public class SensorData {
}
// Check if we shouldn't use a sensor at all.
- if (DEFAULT_SENSOR != selectedSensor) {
+ if (UNSPECIFIED_SENSOR_DATA != selectedSensor) {
if ("".equals(selectedSensor.name) && "".equals(selectedSensor.type)) {
// <proxSensor> with empty values to the config means no sensor should be used.
// See also {@link com.android.server.display.utils.SensorUtils}
@@ -174,7 +165,7 @@ public class SensorData {
* Loads temperature sensor data for no config case. (Type: SKIN, Name: null)
*/
public static SensorData loadTempSensorUnspecifiedConfig() {
- return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+ return new SensorData(TEMPERATURE_TYPE_SKIN, /* name= */ null);
}
/**
@@ -185,7 +176,7 @@ public class SensorData {
DisplayConfiguration config) {
SensorDetails sensorDetails = config.getTempSensor();
if (!flags.isSensorBasedBrightnessThrottlingEnabled() || sensorDetails == null) {
- return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+ return loadTempSensorUnspecifiedConfig();
}
String name = sensorDetails.getName();
String type = sensorDetails.getType();
@@ -202,7 +193,7 @@ public class SensorData {
*/
@NonNull
public static SensorData loadSensorUnspecifiedConfig() {
- return new SensorData();
+ return UNSPECIFIED_SENSOR_DATA;
}
private static SensorData loadSensorData(@NonNull SensorDetails sensorDetails) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1f46af8b741d..bb2efa166800 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2224,12 +2224,6 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
- private void notifyConfigurationChanged(long whenNanos) {
- mWindowManagerCallbacks.notifyConfigurationChanged();
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
synchronized (mInputDevicesLock) {
if (!mInputDevicesChangedPending) {
@@ -2240,6 +2234,9 @@ public class InputManagerService extends IInputManager.Stub
mInputDevices = inputDevices;
}
+ // Input device change can possibly change configuration, so notify window manager to update
+ // its configuration.
+ mWindowManagerCallbacks.notifyConfigurationChanged();
}
// Native callback.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 96ca570fe984..cbe202b5adc8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -22,8 +22,6 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
-import static android.provider.Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE;
-import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED;
@@ -51,6 +49,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
@@ -597,7 +596,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
break;
}
- case STYLUS_HANDWRITING_ENABLED: {
+ case Settings.Secure.STYLUS_HANDWRITING_ENABLED: {
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
InputMethodManager
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
@@ -702,24 +701,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
super(true);
}
- @GuardedBy("ImfLock.class")
- private boolean isChangingPackagesOfCurrentUserLocked() {
- final int userId = getChangingUserId();
- final boolean retval = userId == mCurrentUserId;
- if (DEBUG) {
- if (!retval) {
- Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
- }
- }
- return retval;
- }
-
@Override
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
synchronized (ImfLock.class) {
- if (!isChangingPackagesOfCurrentUserLocked()) {
- return false;
- }
final int userId = getChangingUserId();
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1114,13 +1098,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(userId, newSettings);
- if (mCurrentUserId == userId) {
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
- } else if (mConcurrentMultiUserModeEnabled) {
- initializeVisibleBackgroundUserLocked(userId);
- }
+ // We need to rebuild IMEs.
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
}
}
@@ -1426,16 +1406,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final String defaultImiId = SecureSettingsWrapper.getString(
Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
- currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(currentUserId, newSettings);
+ final var settings = InputMethodSettingsRepository.get(currentUserId);
postInputMethodSettingUpdatedLocked(
!imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId);
updateFromSettingsLocked(true, currentUserId);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
- newSettings.getEnabledInputMethodList());
+ settings.getEnabledInputMethodList());
AdditionalSubtypeMapRepository.startWriterThread();
@@ -1455,9 +1432,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
(windowToken, imeVisible) -> {
if (Flags.refactorInsetsController()) {
if (imeVisible) {
- showSoftInputInternal(windowToken);
+ showCurrentInputInternal(windowToken);
} else {
- hideSoftInputInternal(windowToken);
+ hideCurrentInputInternal(windowToken);
}
}
});
@@ -1506,17 +1483,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
+ if (!mUserManagerInternal.exists(userId)) {
+ return InputMethodInfoSafeList.empty();
+ }
synchronized (ImfLock.class) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mCurrentUserId, null);
- if (resolvedUserIds.length != 1) {
- return InputMethodInfoSafeList.empty();
- }
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
return InputMethodInfoSafeList.create(getInputMethodListLocked(
- resolvedUserIds[0], directBootAwareness, callingUid));
+ userId, directBootAwareness, callingUid));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1531,17 +1506,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
+ if (!mUserManagerInternal.exists(userId)) {
+ return InputMethodInfoSafeList.empty();
+ }
synchronized (ImfLock.class) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mCurrentUserId, null);
- if (resolvedUserIds.length != 1) {
- return InputMethodInfoSafeList.empty();
- }
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
return InputMethodInfoSafeList.create(
- getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid));
+ getEnabledInputMethodListLocked(userId, callingUid));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1557,17 +1530,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
+ if (!mUserManagerInternal.exists(userId)) {
+ return Collections.emptyList();
+ }
synchronized (ImfLock.class) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mCurrentUserId, null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return getInputMethodListLocked(
- resolvedUserIds[0], directBootAwareness, callingUid);
+ return getInputMethodListLocked(userId, directBootAwareness, callingUid);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1582,16 +1552,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
+ if (!mUserManagerInternal.exists(userId)) {
+ return Collections.emptyList();
+ }
synchronized (ImfLock.class) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mCurrentUserId, null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid);
+ return getEnabledInputMethodListLocked(userId, callingUid);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1631,8 +1599,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// If user is a profile, use preference of it`s parent profile.
final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId);
if (Settings.Secure.getIntForUser(context.getContentResolver(),
- STYLUS_HANDWRITING_ENABLED, STYLUS_HANDWRITING_DEFAULT_VALUE,
- profileParentUserId) == 0) {
+ Settings.Secure.STYLUS_HANDWRITING_ENABLED,
+ Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE, profileParentUserId) == 0) {
return false;
}
return true;
@@ -1933,7 +1901,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (Flags.refactorInsetsController()) {
if (isShowRequestedForCurrentWindow(userId) && userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindow != null) {
- showSoftInputInternal(userData.mImeBindingState.mFocusedWindow);
+ showCurrentInputInternal(userData.mImeBindingState.mFocusedWindow);
}
} else {
if (isShowRequestedForCurrentWindow(userId)) {
@@ -3165,8 +3133,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
}
- boolean showSoftInputInternal(IBinder windowToken) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInputInternal");
+ boolean showCurrentInputInternal(IBinder windowToken) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showCurrentInputInternal");
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
@@ -3185,8 +3153,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
}
- boolean hideSoftInputInternal(IBinder windowToken) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInputInternal");
+ boolean hideCurrentInputInternal(IBinder windowToken) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideCurrentInputInternal");
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
@@ -4801,8 +4769,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(
windowToken, userId);
mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
- setVisible ? ImeVisibilityStateComputer.STATE_SHOW_IME
- : ImeVisibilityStateComputer.STATE_HIDE_IME,
+ setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
}
} finally {
@@ -6288,7 +6255,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
boolean isCritical) {
IInputMethodInvoker method;
ClientState client;
- ClientState focusedWindowClient;
final Printer p = new PrintWriterPrinter(pw);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index cbb1807a6c2e..045414bde82e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -17,17 +17,22 @@
package com.android.server.inputmethod;
+import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Printer;
import android.util.Slog;
@@ -80,6 +85,7 @@ final class InputMethodMenuControllerNew {
* @param displayId the ID of the display where the menu was requested.
* @param userId the ID of the user that requested the menu.
*/
+ @RequiresPermission(allOf = {INTERACT_ACROSS_USERS, HIDE_OVERLAY_WINDOWS})
void show(@NonNull List<MenuItem> items, int selectedIndex, int displayId,
@UserIdInt int userId) {
// Hide the menu in case it was already showing.
@@ -120,7 +126,7 @@ final class InputMethodMenuControllerNew {
.requireViewById(com.android.internal.R.id.button1);
languageSettingsButton.setVisibility(View.VISIBLE);
languageSettingsButton.setOnClickListener(v -> {
- v.getContext().startActivity(languageSettingsIntent);
+ v.getContext().startActivityAsUser(languageSettingsIntent, UserHandle.of(userId));
hide(displayId, userId);
});
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 3d0b079c69c8..741513cf3c0b 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -616,9 +616,10 @@ public class LocaleManagerService extends SystemService {
LocaleConfig resLocaleConfig = null;
try {
resLocaleConfig = LocaleConfig.fromContextIgnoringOverride(
- mContext.createPackageContext(appPackageName, 0));
+ mContext.createPackageContextAsUser(appPackageName, /* flags= */ 0,
+ UserHandle.of(userId)));
} catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Unknown package name " + appPackageName);
+ Slog.e(TAG, "Unknown package name " + appPackageName + " for user " + userId);
return;
}
final File file = getXmlFileNameForUser(appPackageName, userId);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 6e991b4db2b1..2e167efc4d81 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -376,6 +376,11 @@ public class LocationManagerService extends ILocationManager.Stub implements
mContext.getContentResolver(),
Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
defaultStationaryThrottlingSetting) != 0;
+ if (Flags.disableStationaryThrottling() && !(
+ Flags.keepGnssStationaryThrottling() && enableStationaryThrottling
+ && GPS_PROVIDER.equals(manager.getName()))) {
+ enableStationaryThrottling = false;
+ }
if (enableStationaryThrottling) {
realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
mInjector, realProvider);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index ed451ff0c194..3f4a9bb4d864 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -297,7 +297,10 @@ public class ContextHubService extends IContextHubService.Stub {
}
public boolean isExpired() {
- return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos()
+ return mTimestamp
+ + ContextHubTransactionManager
+ .RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT
+ .toNanos()
< SystemClock.elapsedRealtimeNanos();
}
}
@@ -333,8 +336,14 @@ public class ContextHubService extends IContextHubService.Stub {
return new IContextHubClientCallback.Stub() {
private void finishCallback() {
try {
- IContextHubClient client = mDefaultClientMap.get(contextHubId);
- client.callbackFinished();
+ if (mDefaultClientMap != null && mDefaultClientMap.containsKey(contextHubId)) {
+ IContextHubClient client = mDefaultClientMap.get(contextHubId);
+ client.callbackFinished();
+ } else {
+ Log.e(TAG, "Default client not found for hub (ID = " + contextHubId + "): "
+ + mDefaultClientMap == null ? "map was null"
+ : "map did not contain the hub");
+ }
} catch (RemoteException e) {
Log.e(
TAG,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index e6d330f85dfc..cd69ebaba766 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -56,6 +56,9 @@ import java.util.concurrent.atomic.AtomicInteger;
public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1);
+ public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT =
+ RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3);
+
private static final int MAX_PENDING_REQUESTS = 10000;
private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 17f2fcc5b9d8..bb35b378866b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -340,6 +340,11 @@ class LockSettingsShellCommand extends ShellCommand {
getOutPrintWriter().println("Profile uses unified challenge");
return false;
}
+ if (mOld.isEmpty()) {
+ getOutPrintWriter().println(
+ "User has a lock credential, but old credential was not provided");
+ return false;
+ }
try {
final boolean result = mLockPatternUtils.checkCredential(getOldCredential(),
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 803b125cbabc..621c090d37b8 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -702,7 +702,7 @@ public final class MediaProjectionManagerService extends SystemService
}
}
- private final class BinderService extends IMediaProjectionManager.Stub {
+ final class BinderService extends IMediaProjectionManager.Stub {
BinderService(Context context) {
super(PermissionEnforcer.fromContext(context));
@@ -891,6 +891,13 @@ public final class MediaProjectionManagerService extends SystemService
@Override
public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
requestConsentForInvalidProjection_enforcePermission();
+
+ if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()
+ && mKeyguardManager.isKeyguardLocked()) {
+ Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked");
+ return;
+ }
+
synchronized (mLock) {
if (!isCurrentProjection(projection)) {
Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 5ea3e70f7957..74f0d9cf3e39 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -81,8 +81,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.HexDump;
-import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -833,144 +831,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
}
@Override
- public boolean getIpForwardingEnabled() throws IllegalStateException{
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException(
- "NMS#getIpForwardingEnabled not supported in V+");
- }
- try {
- return mNetdService.ipfwdEnabled();
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void setIpForwardingEnabled(boolean enable) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException(
- "NMS#setIpForwardingEnabled not supported in V+");
- } try {
- if (enable) {
- mNetdService.ipfwdEnableForwarding("tethering");
- } else {
- mNetdService.ipfwdDisableForwarding("tethering");
- }
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void startTethering(String[] dhcpRange) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#startTethering not supported in V+");
- }
- try {
- NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void stopTethering() {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#stopTethering not supported in V+");
- }
- try {
- mNetdService.tetherStop();
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public boolean isTetheringStarted() {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#isTetheringStarted not supported in V+");
- }
- try {
- return mNetdService.tetherIsEnabled();
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void tetherInterface(String iface) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#tetherInterface not supported in V+");
- }
- try {
- final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
- final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
- NetdUtils.tetherInterface(mNetdService, iface, dest);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void untetherInterface(String iface) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#untetherInterface not supported in V+");
- }
- try {
- NetdUtils.untetherInterface(mNetdService, iface);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public String[] listTetheredInterfaces() {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException(
- "NMS#listTetheredInterfaces not supported in V+");
- }
- try {
- return mNetdService.tetherInterfaceList();
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void enableNat(String internalInterface, String externalInterface) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#enableNat not supported in V+");
- }
- try {
- mNetdService.tetherAddForward(internalInterface, externalInterface);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void disableNat(String internalInterface, String externalInterface) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
- if (SdkLevel.isAtLeastV()) {
- throw new UnsupportedOperationException("NMS#disableNat not supported in V+");
- }
- try {
- mNetdService.tetherRemoveForward(internalInterface, externalInterface);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
public void setInterfaceQuota(String iface, long quotaBytes) {
PermissionUtils.enforceNetworkStackPermission(mContext);
@@ -1126,30 +986,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
}
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDataSaverModeEnabled");
try {
- if (SdkLevel.isAtLeastV()) {
- // setDataSaverEnabled throws if it fails to set data saver.
- mContext.getSystemService(ConnectivityManager.class)
- .setDataSaverEnabled(enable);
- mDataSaverMode = enable;
- if (mUseMeteredFirewallChains) {
- // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW
- // until ConnectivityService allows manipulation of the data saver mode via
- // FIREWALL_CHAIN_METERED_ALLOW.
- synchronized (mRulesLock) {
- mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable);
- }
- }
- return true;
- } else {
- final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
- if (changed) {
- mDataSaverMode = enable;
- } else {
- Log.e(TAG, "setDataSaverMode(" + enable + "): failed to set iptables");
+ // setDataSaverEnabled throws if it fails to set data saver.
+ mContext.getSystemService(ConnectivityManager.class).setDataSaverEnabled(enable);
+ mDataSaverMode = enable;
+ if (mUseMeteredFirewallChains) {
+ // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW
+ // until ConnectivityService allows manipulation of the data saver mode via
+ // FIREWALL_CHAIN_METERED_ALLOW.
+ synchronized (mRulesLock) {
+ mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable);
}
- return changed;
}
- } catch (RemoteException | IllegalStateException e) {
+ return true;
+ } catch (IllegalStateException e) {
Log.e(TAG, "setDataSaverMode(" + enable + "): failed with exception", e);
return false;
} finally {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d9e22c5a270f..53b67969e91a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4118,7 +4118,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
fout.increaseIndent();
for (int i = 0; i < mSubscriptionPlans.size(); i++) {
final int subId = mSubscriptionPlans.keyAt(i);
- fout.println("Subscriber ID " + subId + ":");
+ fout.println("Subscription ID " + subId + ":");
fout.increaseIndent();
final SubscriptionPlan[] plans = mSubscriptionPlans.valueAt(i);
if (!ArrayUtils.isEmpty(plans)) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c078409f468c..b12a917eede9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1159,11 +1159,18 @@ public class ZenModeHelper {
rule.conditionId = azr.getConditionId();
modified = true;
}
- boolean shouldPreserveCondition = Flags.modesApi() && Flags.modesUi()
- && !isNew && origin == UPDATE_ORIGIN_USER
- && rule.enabled == azr.isEnabled()
- && rule.conditionId != null && rule.condition != null
- && rule.conditionId.equals(rule.condition.id);
+ // This can be removed when {@link Flags#modesUi} is fully ramped up
+ final boolean isWatch =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ boolean shouldPreserveCondition =
+ Flags.modesApi()
+ && (Flags.modesUi() || isWatch)
+ && !isNew
+ && origin == UPDATE_ORIGIN_USER
+ && rule.enabled == azr.isEnabled()
+ && rule.conditionId != null
+ && rule.condition != null
+ && rule.conditionId.equals(rule.condition.id);
if (!shouldPreserveCondition) {
// Do not update 'modified'. If only this changes we treat it as a no-op updateAZR.
rule.condition = null;
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index 58b14b14fdef..15e758cf6ffd 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -37,7 +37,6 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.util.Log;
import com.android.internal.R;
@@ -47,9 +46,9 @@ public class BackgroundUserSoundNotifier {
private static final boolean DEBUG = false;
private static final String LOG_TAG = BackgroundUserSoundNotifier.class.getSimpleName();
- public static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
- public static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
- private static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
+ private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
+ private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
+ public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID";
private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID";
private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER";
@@ -144,6 +143,7 @@ public class BackgroundUserSoundNotifier {
-1) + " current user id " + intent.getIntExtra(
EXTRA_CURRENT_USER_ID, -1));
}
+ mUserWithNotification = -1;
mNotificationManager.cancelAsUser(LOG_TAG, notificationId,
UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1)));
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
@@ -159,10 +159,6 @@ public class BackgroundUserSoundNotifier {
}
}
}
- Vibrator vibrator = mSystemUserContext.getSystemService(Vibrator.class);
- if (vibrator != null && vibrator.isVibrating()) {
- vibrator.cancel();
- }
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
}
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index 8175321ea293..9a7ba0f082ea 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -104,6 +104,7 @@ public class SaferIntentUtils {
@Disabled
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
+ @Nullable
private static ParsedMainComponent infoToComponent(
ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) {
if (info instanceof ActivityInfo) {
@@ -186,7 +187,7 @@ public class SaferIntentUtils {
}
boolean isChangeEnabled(long changeId) {
- return platformCompat == null || platformCompat.isChangeEnabledByUidInternal(
+ return platformCompat == null || platformCompat.isChangeEnabledByUidInternalNoLogging(
changeId, callingUid);
}
@@ -233,7 +234,8 @@ public class SaferIntentUtils {
}
final ParsedMainComponent comp = infoToComponent(
resolveInfo.getComponentInfo(), resolver, args.isReceiver);
- if (!comp.getIntents().isEmpty() && args.intent.getAction() == null) {
+ if (comp != null && !comp.getIntents().isEmpty()
+ && args.intent.getAction() == null) {
match = false;
}
} else if (c instanceof IntentFilter) {
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index fde23b726572..9b644880d737 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.role.RoleManager;
import android.content.ActivityNotFoundException;
@@ -248,31 +249,7 @@ public class ModifierShortcutManager {
+ " className=" + className + " shortcutChar=" + shortcutChar);
continue;
}
- ComponentName componentName = new ComponentName(packageName, className);
- try {
- mPackageManager.getActivityInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_UNINSTALLED_PACKAGES);
- } catch (PackageManager.NameNotFoundException e) {
- String[] packages = mPackageManager.canonicalToCurrentPackageNames(
- new String[] { packageName });
- componentName = new ComponentName(packages[0], className);
- try {
- mPackageManager.getActivityInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_UNINSTALLED_PACKAGES);
- } catch (PackageManager.NameNotFoundException e1) {
- Log.w(TAG, "Unable to add bookmark: " + packageName
- + "/" + className + " not found.");
- continue;
- }
- }
-
- intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(componentName);
+ intent = resolveComponentNameIntent(packageName, className);
} else if (categoryName != null) {
if (roleName != null) {
Log.w(TAG, "Cannot specify role bookmark when category is present for"
@@ -310,6 +287,32 @@ public class ModifierShortcutManager {
}
}
+ @Nullable
+ private Intent resolveComponentNameIntent(String packageName, String className) {
+ int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+ ComponentName componentName = new ComponentName(packageName, className);
+ try {
+ mPackageManager.getActivityInfo(componentName, flags);
+ } catch (PackageManager.NameNotFoundException e) {
+ String[] packages = mPackageManager.canonicalToCurrentPackageNames(
+ new String[] { packageName });
+ componentName = new ComponentName(packages[0], className);
+ try {
+ mPackageManager.getActivityInfo(componentName, flags);
+ } catch (PackageManager.NameNotFoundException e1) {
+ Log.w(TAG, "Unable to add bookmark: " + packageName
+ + "/" + className + " not found.");
+ return null;
+ }
+ }
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(componentName);
+ return intent;
+ }
+
void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
IShortcutService service = mShortcutKeyServices.get(shortcutCode);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8419a608dc41..df973135f5f0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2402,7 +2402,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
}
- mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
+ final var transitionListener = new AppTransitionListener(DEFAULT_DISPLAY) {
@Override
public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
@@ -2436,7 +2436,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mLockAfterDreamingTransitionFinished = false;
}
}
- });
+ };
+ mWindowManagerInternal.registerAppTransitionListener(transitionListener);
mKeyguardDrawnTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_keyguardDrawnTimeout);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
index ad146afe16e7..fb54c5de260a 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
@@ -20,6 +20,8 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
+import com.android.server.power.optimization.Flags;
+
public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDumpHelper {
private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
@@ -33,6 +35,9 @@ public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDump
.setMaxStatsAgeMs(0);
if (detailed) {
builder.includePowerModels().includeProcessStateData().includeVirtualUids();
+ if (Flags.batteryUsageStatsByPowerAndScreenState()) {
+ builder.includePowerStateData().includeScreenStateData();
+ }
}
return mBatteryUsageStatsProvider.getBatteryUsageStats((BatteryStatsImpl) batteryStats,
builder.build());
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 1b7bf89d7b44..4052a64aabba 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16356,6 +16356,7 @@ public class BatteryStatsImpl extends BatteryStats {
mBluetoothPowerStatsCollector.collectAndDump(pw);
mCameraPowerStatsCollector.collectAndDump(pw);
mGnssPowerStatsCollector.collectAndDump(pw);
+ mCustomEnergyConsumerPowerStatsCollector.collectAndDump(pw);
}
private final Runnable mWriteAsyncRunnable = () -> {
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 8127b8217bb0..ac6896696de6 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -201,7 +201,8 @@ public class BatteryUsageStatsProvider {
batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
stats.getCustomEnergyConsumerNames(), includePowerModels,
- includeProcessStateData, minConsumedPowerThreshold);
+ includeProcessStateData, query.isScreenStateDataNeeded(),
+ query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
// TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
// of batteryUsageStats sessions to wall-clock adjustments
@@ -348,6 +349,7 @@ public class BatteryUsageStatsProvider {
final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+ query.isScreenStateDataNeeded(), query.isPowerStateDataNeeded(),
minConsumedPowerThreshold);
if (mPowerStatsStore == null) {
Log.e(TAG, "PowerStatsStore is unavailable");
@@ -408,7 +410,6 @@ public class BatteryUsageStatsProvider {
+ " does not include process state data");
continue;
}
-
builder.add(snapshot);
}
}
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
index 11919019be4a..0273ba6a6d18 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
@@ -19,6 +19,7 @@ package com.android.server.power.stats;
import android.hardware.power.stats.EnergyConsumerType;
import android.os.BatteryConsumer;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -65,4 +66,12 @@ public class CustomEnergyConsumerPowerStatsCollector extends PowerStatsCollector
}
return success;
}
+
+ @Override
+ public void collectAndDump(PrintWriter pw) {
+ ensureInitialized();
+ for (int i = 0; i < mCollectors.size(); i++) {
+ mCollectors.get(i).collectAndDump(pw);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
index cace94114aa0..ce11fa0edaf7 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -176,9 +176,12 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
for (EnergyConsumerAttribution attribution : perUid) {
int uid = mUidResolver.mapUid(attribution.uid);
- long lastEnergy = mLastConsumerEnergyPerUid.get(uid);
- long deltaEnergy = attribution.energyUWs - lastEnergy;
+ long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED);
mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs);
+ if (lastEnergy == ENERGY_UNSPECIFIED) {
+ continue;
+ }
+ long deltaEnergy = attribution.energyUWs - lastEnergy;
if (deltaEnergy <= 0) {
continue;
}
@@ -189,7 +192,8 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
}
mLayout.setUidConsumedEnergy(uidStats, 0,
- mLayout.getUidConsumedEnergy(uidStats, 0) + deltaEnergy);
+ mLayout.getUidConsumedEnergy(uidStats, 0)
+ + uJtoUc(deltaEnergy, averageVoltage));
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index d9f6c1ff1444..f5b00054bea4 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -196,12 +196,11 @@ public abstract class PowerStatsCollector {
}
IndentingPrintWriter out = new IndentingPrintWriter(pw);
- out.print(getClass().getSimpleName());
if (!isEnabled()) {
+ out.print(getClass().getSimpleName());
out.println(": disabled");
return;
}
- out.println();
ArrayList<PowerStats> collected = new ArrayList<>();
Consumer<PowerStats> consumer = collected::add;
@@ -215,11 +214,9 @@ public abstract class PowerStatsCollector {
removeConsumer(consumer);
}
- out.increaseIndent();
for (PowerStats stats : collected) {
stats.dump(out);
}
- out.decreaseIndent();
}
private void awaitCompletion() {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 549a97ea49cd..0f1349287a0f 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -129,17 +129,55 @@ public class PowerStatsExporter {
if (descriptor == null) {
return;
}
+ boolean isCustomComponent =
+ descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
PowerStatsLayout layout = new PowerStatsLayout();
layout.fromExtras(descriptor.extras);
long[] deviceStats = new long[descriptor.statsArrayLength];
+ for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
+ if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
+ if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ continue;
+ }
+ } else if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) {
+ if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ continue;
+ }
+ } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) {
+ continue;
+ }
+
+ populateAggregatedBatteryConsumer(batteryUsageStatsBuilder, powerComponentStats,
+ layout, deviceStats, screenState, powerState);
+ }
+ }
+ if (layout.isUidPowerAttributionSupported()) {
+ populateBatteryConsumers(batteryUsageStatsBuilder,
+ powerComponentStats, layout);
+ }
+ }
+
+ private static void populateAggregatedBatteryConsumer(
+ BatteryUsageStats.Builder batteryUsageStatsBuilder,
+ PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
+ long[] deviceStats, @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState) {
+ int powerComponentId = powerComponentStats.powerComponentId;
+ boolean isCustomComponent =
+ powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+
double[] totalPower = new double[1];
MultiStateStats.States.forEachTrackedStateCombination(
powerComponentStats.getConfig().getDeviceStateConfig(),
states -> {
- if (states[AggregatedPowerStatsConfig.STATE_POWER]
- != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+ if (!areMatchingStates(states, screenState, powerState)) {
return;
}
@@ -153,24 +191,23 @@ public class PowerStatsExporter {
AggregateBatteryConsumer.Builder deviceScope =
batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
- if (descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(
- descriptor.powerComponentId)) {
- deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId,
- totalPower[0]);
+ if (isCustomComponent) {
+ if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
+ deviceScope.addConsumedPowerForCustomComponent(powerComponentId, totalPower[0]);
}
} else {
- deviceScope.addConsumedPower(descriptor.powerComponentId,
- totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
-
- if (layout.isUidPowerAttributionSupported()) {
- populateUidBatteryConsumers(batteryUsageStatsBuilder,
- powerComponentStats, layout);
+ BatteryConsumer.Key key = deviceScope.getKey(powerComponentId,
+ BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
+ if (key != null) {
+ deviceScope.addConsumedPower(key, totalPower[0],
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
+ deviceScope.addConsumedPower(powerComponentId, totalPower[0],
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
}
}
- private static void populateUidBatteryConsumers(
+ private static void populateBatteryConsumers(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats,
PowerStatsLayout layout) {
@@ -185,11 +222,44 @@ public class PowerStatsExporter {
.getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked()
&& powerComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ ArrayList<Integer> uids = new ArrayList<>();
+ powerComponentStats.collectUids(uids);
+ for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
+ if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
+ if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ continue;
+ }
+ } else if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+ if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ continue;
+ }
+ } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) {
+ continue;
+ }
+
+ populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponentStats, layout,
+ uids, powerComponent, uidStats, breakDownByProcState, screenState,
+ powerState);
+ }
+ }
+ }
+
+ private static void populateUidBatteryConsumers(
+ BatteryUsageStats.Builder batteryUsageStatsBuilder,
+ PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
+ List<Integer> uids, AggregatedPowerStatsConfig.PowerComponent powerComponent,
+ long[] uidStats, boolean breakDownByProcState,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState) {
+ int powerComponentId = powerComponentStats.powerComponentId;
double[] powerByProcState =
new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
double powerAllApps = 0;
- ArrayList<Integer> uids = new ArrayList<>();
- powerComponentStats.collectUids(uids);
for (int uid : uids) {
UidBatteryConsumer.Builder builder =
batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
@@ -199,8 +269,7 @@ public class PowerStatsExporter {
MultiStateStats.States.forEachTrackedStateCombination(
powerComponent.getUidStateConfig(),
states -> {
- if (states[AggregatedPowerStatsConfig.STATE_POWER]
- != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+ if (!areMatchingStates(states, screenState, powerState)) {
return;
}
@@ -224,8 +293,17 @@ public class PowerStatsExporter {
powerAllProcStates += power;
if (breakDownByProcState
&& procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- builder.addConsumedPower(builder.getKey(powerComponentId, procState), power,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+ builder.addConsumedPower(
+ builder.getKey(powerComponentId, procState, screenState,
+ powerState),
+ power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+ } else {
+ builder.addConsumedPower(
+ builder.getKey(powerComponentId, procState, screenState,
+ BatteryConsumer.POWER_STATE_UNSPECIFIED),
+ power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
}
}
if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
@@ -243,8 +321,49 @@ public class PowerStatsExporter {
if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
} else {
+ BatteryConsumer.Key key = allAppsScope.getKey(powerComponentId,
+ BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
+ if (key != null) {
+ allAppsScope.addConsumedPower(key, powerAllApps,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
BatteryConsumer.POWER_MODEL_UNDEFINED);
}
}
+
+ private static boolean areMatchingStates(int[] states,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState) {
+ switch (screenState) {
+ case BatteryConsumer.SCREEN_STATE_ON:
+ if (states[AggregatedPowerStatsConfig.STATE_SCREEN]
+ != AggregatedPowerStatsConfig.SCREEN_STATE_ON) {
+ return false;
+ }
+ break;
+ case BatteryConsumer.SCREEN_STATE_OTHER:
+ if (states[AggregatedPowerStatsConfig.STATE_SCREEN]
+ != AggregatedPowerStatsConfig.SCREEN_STATE_OTHER) {
+ return false;
+ }
+ break;
+ }
+
+ switch (powerState) {
+ case BatteryConsumer.POWER_STATE_BATTERY:
+ if (states[AggregatedPowerStatsConfig.STATE_POWER]
+ != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+ return false;
+ }
+ break;
+ case BatteryConsumer.POWER_STATE_OTHER:
+ if (states[AggregatedPowerStatsConfig.STATE_POWER]
+ != AggregatedPowerStatsConfig.POWER_STATE_OTHER) {
+ return false;
+ }
+ break;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index d34498a3764b..cc0a283db6a0 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -54,3 +54,17 @@ flag {
description: "Adds battery_usage_stats_slice atom"
bug: "324602949"
}
+
+flag {
+ name: "battery_usage_stats_by_power_and_screen_state"
+ namespace: "backstage_power"
+ description: "Batterystats dumpsys is enhanced by including power break-down by power s"
+ bug: "352835319"
+}
+
+flag {
+ name: "disable_composite_battery_usage_stats_atoms"
+ namespace: "backstage_power"
+ description: "Disable deprecated BatteryUsageStatsAtom pulled atom"
+ bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 6537228583ec..5fab13bdc402 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -67,6 +67,7 @@ abstract class Vibration {
CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+ CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 4437a2ddf3a7..bff175fec1dd 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_CLASS_ALARM;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -73,6 +74,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.SystemService;
+import com.android.server.pm.BackgroundUserSoundNotifier;
import libcore.util.NativeAllocationRegistry;
@@ -173,7 +175,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @VisibleForTesting
+ BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
@@ -190,6 +193,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/* immediate= */ false);
}
}
+ } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
+ && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
+ synchronized (mLock) {
+ if (shouldCancelOnFgUserRequest(mNextVibration)) {
+ clearNextVibrationLocked(new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_FOREGROUND_USER));
+ }
+ if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
+ mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_FOREGROUND_USER),
+ /* immediate= */ false);
+ }
+ }
}
}
};
@@ -299,6 +315,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
+ if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()) {
+ filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND);
+ }
context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
@@ -1423,6 +1442,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
+ private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
+ if (conductor == null) {
+ return false;
+ }
+ return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+ }
+
+ @GuardedBy("mLock")
private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
for (int i = 0; i < mVibrators.size(); i++) {
consumer.accept(mVibrators.valueAt(i));
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index a821f54520df..c4d601d03652 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -67,19 +67,13 @@ public class SystemImpl implements SystemInterface {
private static final String TAG_SIGNATURE = "signature";
private static final String TAG_FALLBACK = "isFallback";
private static final String PIN_GROUP = "webview";
- private final WebViewProviderInfo[] mWebViewProviderPackages;
- // Initialization-on-demand holder idiom for getting the WebView provider packages once and
- // for all in a thread-safe manner.
- private static class LazyHolder {
- private static final SystemImpl INSTANCE = new SystemImpl();
- }
+ private final Context mContext;
+ private final WebViewProviderInfo[] mWebViewProviderPackages;
- public static SystemImpl getInstance() {
- return LazyHolder.INSTANCE;
- }
+ SystemImpl(Context context) {
+ mContext = context;
- private SystemImpl() {
int numFallbackPackages = 0;
int numAvailableByDefaultPackages = 0;
XmlResourceParser parser = null;
@@ -184,14 +178,14 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public String getUserChosenWebViewProvider(Context context) {
- return Settings.Global.getString(context.getContentResolver(),
+ public String getUserChosenWebViewProvider() {
+ return Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.WEBVIEW_PROVIDER);
}
@Override
- public void updateUserSetting(Context context, String newProviderName) {
- Settings.Global.putString(context.getContentResolver(),
+ public void updateUserSetting(String newProviderName) {
+ Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.WEBVIEW_PROVIDER,
newProviderName == null ? "" : newProviderName);
}
@@ -207,8 +201,8 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
- UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
+ public void enablePackageForAllUsers(String packageName, boolean enable) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
for(UserInfo userInfo : userManager.getUsers()) {
enablePackageForUser(packageName, enable, userInfo.id);
}
@@ -228,16 +222,15 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public void installExistingPackageForAllUsers(Context context, String packageName) {
- UserManager userManager = context.getSystemService(UserManager.class);
+ public void installExistingPackageForAllUsers(String packageName) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
for (UserInfo userInfo : userManager.getUsers()) {
installPackageForUser(packageName, userInfo.id);
}
}
private void installPackageForUser(String packageName, int userId) {
- final Context context = AppGlobals.getInitialApplication();
- final Context contextAsUser = context.createContextAsUser(UserHandle.of(userId), 0);
+ final Context contextAsUser = mContext.createContextAsUser(UserHandle.of(userId), 0);
final PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
installer.installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN, null);
}
@@ -255,29 +248,28 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
- WebViewProviderInfo configInfo) {
- return UserPackage.getPackageInfosAllUsers(context, configInfo.packageName, PACKAGE_FLAGS);
+ public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo) {
+ return UserPackage.getPackageInfosAllUsers(mContext, configInfo.packageName, PACKAGE_FLAGS);
}
@Override
- public int getMultiProcessSetting(Context context) {
+ public int getMultiProcessSetting() {
if (updateServiceV2()) {
throw new IllegalStateException(
"getMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
}
return Settings.Global.getInt(
- context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
+ mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
}
@Override
- public void setMultiProcessSetting(Context context, int value) {
+ public void setMultiProcessSetting(int value) {
if (updateServiceV2()) {
throw new IllegalStateException(
"setMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
}
Settings.Global.putInt(
- context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
+ mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
}
@Override
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index ad32f623c80d..3b77d07412ce 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -16,7 +16,6 @@
package com.android.server.webkit;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -34,19 +33,19 @@ import java.util.List;
* @hide
*/
public interface SystemInterface {
- public WebViewProviderInfo[] getWebViewPackages();
- public int onWebViewProviderChanged(PackageInfo packageInfo);
- public long getFactoryPackageVersion(String packageName) throws NameNotFoundException;
+ WebViewProviderInfo[] getWebViewPackages();
+ int onWebViewProviderChanged(PackageInfo packageInfo);
+ long getFactoryPackageVersion(String packageName) throws NameNotFoundException;
- public String getUserChosenWebViewProvider(Context context);
- public void updateUserSetting(Context context, String newProviderName);
- public void killPackageDependents(String packageName);
+ String getUserChosenWebViewProvider();
+ void updateUserSetting(String newProviderName);
+ void killPackageDependents(String packageName);
- public void enablePackageForAllUsers(Context context, String packageName, boolean enable);
- public void installExistingPackageForAllUsers(Context context, String packageName);
+ void enablePackageForAllUsers(String packageName, boolean enable);
+ void installExistingPackageForAllUsers(String packageName);
- public boolean systemIsDebuggable();
- public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
+ boolean systemIsDebuggable();
+ PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
/**
* Get the PackageInfos of all users for the package represented by {@param configInfo}.
@@ -54,15 +53,14 @@ public interface SystemInterface {
* certain user. The returned array can contain null PackageInfos if the given package
* is uninstalled for some user.
*/
- public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
- WebViewProviderInfo configInfo);
+ List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo);
- public int getMultiProcessSetting(Context context);
- public void setMultiProcessSetting(Context context, int value);
- public void notifyZygote(boolean enableMultiProcess);
+ int getMultiProcessSetting();
+ void setMultiProcessSetting(int value);
+ void notifyZygote(boolean enableMultiProcess);
/** Start the zygote if it's not already running. */
- public void ensureZygoteStarted();
- public boolean isMultiProcessDefaultEnabled();
+ void ensureZygoteStarted();
+ boolean isMultiProcessDefaultEnabled();
- public void pinWebviewIfRequired(ApplicationInfo appInfo);
+ void pinWebviewIfRequired(ApplicationInfo appInfo);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 043470f62850..7acb864cbb40 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -73,9 +73,9 @@ public class WebViewUpdateService extends SystemService {
public WebViewUpdateService(Context context) {
super(context);
if (updateServiceV2()) {
- mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance());
+ mImpl = new WebViewUpdateServiceImpl2(new SystemImpl(context));
} else {
- mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+ mImpl = new WebViewUpdateServiceImpl(new SystemImpl(context));
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index dcf20f97ef71..b9be4a2deef2 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.webkit;
import android.annotation.Nullable;
-import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
@@ -92,7 +91,6 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
private final SystemInterface mSystemInterface;
- private final Context mContext;
private long mMinimumVersionCode = -1;
@@ -110,8 +108,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
private final Object mLock = new Object();
- WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
- mContext = context;
+ WebViewUpdateServiceImpl(SystemInterface systemInterface) {
mSystemInterface = systemInterface;
}
@@ -173,7 +170,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
try {
synchronized (mLock) {
mCurrentWebViewPackage = findPreferredWebViewPackage();
- String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ String userSetting = mSystemInterface.getUserChosenWebViewProvider();
if (userSetting != null
&& !userSetting.equals(mCurrentWebViewPackage.packageName)) {
// Don't persist the user-chosen setting across boots if the package being
@@ -181,8 +178,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
// be surprised by the device switching to using a certain webview package,
// that was uninstalled/disabled a long time ago, if it is installed/enabled
// again.
- mSystemInterface.updateUserSetting(mContext,
- mCurrentWebViewPackage.packageName);
+ mSystemInterface.updateUserSetting(mCurrentWebViewPackage.packageName);
}
onWebViewProviderChanged(mCurrentWebViewPackage);
}
@@ -203,8 +199,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
if (fallbackProvider != null) {
Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
- mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
- true);
+ mSystemInterface.enablePackageForAllUsers(fallbackProvider.packageName, true);
} else {
Slog.e(TAG, "No valid provider and no fallback available.");
}
@@ -316,7 +311,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
oldPackage = mCurrentWebViewPackage;
if (newProviderName != null) {
- mSystemInterface.updateUserSetting(mContext, newProviderName);
+ mSystemInterface.updateUserSetting(newProviderName);
}
try {
@@ -447,7 +442,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
- String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider();
// If the user has chosen provider, use that (if it's installed and enabled for all
// users).
@@ -455,7 +450,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
// userPackages can contain null objects.
List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ mSystemInterface.getPackageInfoForProviderAllUsers(
providerAndPackage.provider);
if (isInstalledAndEnabledForAllUsers(userPackages)) {
return providerAndPackage.packageInfo;
@@ -470,7 +465,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
if (providerAndPackage.provider.availableByDefault) {
// userPackages can contain null objects.
List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ mSystemInterface.getPackageInfoForProviderAllUsers(
providerAndPackage.provider);
if (isInstalledAndEnabledForAllUsers(userPackages)) {
return providerAndPackage.packageInfo;
@@ -658,7 +653,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
@Override
public boolean isMultiProcessEnabled() {
- int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+ int settingValue = mSystemInterface.getMultiProcessSetting();
if (mSystemInterface.isMultiProcessDefaultEnabled()) {
// Multiprocess should be enabled unless the user has turned it off manually.
return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
@@ -671,7 +666,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
@Override
public void enableMultiProcess(boolean enable) {
PackageInfo current = getCurrentWebViewPackage();
- mSystemInterface.setMultiProcessSetting(mContext,
+ mSystemInterface.setMultiProcessSetting(
enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
mSystemInterface.notifyZygote(enable);
if (current != null) {
@@ -725,7 +720,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
pw.println(" WebView packages:");
for (WebViewProviderInfo provider : allProviders) {
List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+ mSystemInterface.getPackageInfoForProviderAllUsers(provider);
PackageInfo systemUserPackageInfo =
userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
if (systemUserPackageInfo == null) {
@@ -741,7 +736,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
systemUserPackageInfo.applicationInfo.targetSdkVersion);
if (validity == VALIDITY_OK) {
boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
+ mSystemInterface.getPackageInfoForProviderAllUsers(provider));
pw.println(String.format(
" Valid package %s (%s) is %s installed/enabled for all users",
systemUserPackageInfo.packageName,
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 993597eedd2c..307c15b72c76 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -16,7 +16,6 @@
package com.android.server.webkit;
import android.annotation.Nullable;
-import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
@@ -86,7 +85,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
private final SystemInterface mSystemInterface;
- private final Context mContext;
private final WebViewProviderInfo mDefaultProvider;
private long mMinimumVersionCode = -1;
@@ -108,8 +106,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
private final Object mLock = new Object();
- WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) {
- mContext = context;
+ WebViewUpdateServiceImpl2(SystemInterface systemInterface) {
mSystemInterface = systemInterface;
WebViewProviderInfo[] webviewProviders = getWebViewPackages();
@@ -194,8 +191,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
}
if (mCurrentWebViewPackage.packageName.equals(mDefaultProvider.packageName)) {
List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(
- mContext, mDefaultProvider);
+ mSystemInterface.getPackageInfoForProviderAllUsers(mDefaultProvider);
return !isInstalledAndEnabledForAllUsers(userPackages);
} else {
return false;
@@ -216,10 +212,8 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
TAG,
"No provider available for all users, trying to install and enable "
+ mDefaultProvider.packageName);
- mSystemInterface.installExistingPackageForAllUsers(
- mContext, mDefaultProvider.packageName);
- mSystemInterface.enablePackageForAllUsers(
- mContext, mDefaultProvider.packageName, true);
+ mSystemInterface.installExistingPackageForAllUsers(mDefaultProvider.packageName);
+ mSystemInterface.enablePackageForAllUsers(mDefaultProvider.packageName, true);
}
@Override
@@ -229,7 +223,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
synchronized (mLock) {
mCurrentWebViewPackage = findPreferredWebViewPackage();
repairNeeded = shouldTriggerRepairLocked();
- String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ String userSetting = mSystemInterface.getUserChosenWebViewProvider();
if (userSetting != null
&& !userSetting.equals(mCurrentWebViewPackage.packageName)) {
// Don't persist the user-chosen setting across boots if the package being
@@ -237,8 +231,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
// be surprised by the device switching to using a certain webview package,
// that was uninstalled/disabled a long time ago, if it is installed/enabled
// again.
- mSystemInterface.updateUserSetting(mContext,
- mCurrentWebViewPackage.packageName);
+ mSystemInterface.updateUserSetting(mCurrentWebViewPackage.packageName);
}
onWebViewProviderChanged(mCurrentWebViewPackage);
}
@@ -362,7 +355,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
oldPackage = mCurrentWebViewPackage;
if (newProviderName != null) {
- mSystemInterface.updateUserSetting(mContext, newProviderName);
+ mSystemInterface.updateUserSetting(newProviderName);
}
try {
@@ -493,7 +486,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
Counter.logIncrement("webview.value_find_preferred_webview_package_counter");
// If the user has chosen provider, use that (if it's installed and enabled for all
// users).
- String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider();
WebViewProviderInfo userChosenProvider =
getWebViewProviderForPackage(userChosenPackageName);
if (userChosenProvider != null) {
@@ -502,8 +495,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
mSystemInterface.getPackageInfoForProvider(userChosenProvider);
if (validityResult(userChosenProvider, packageInfo) == VALIDITY_OK) {
List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(
- mContext, userChosenProvider);
+ mSystemInterface.getPackageInfoForProviderAllUsers(userChosenProvider);
if (isInstalledAndEnabledForAllUsers(userPackages)) {
return packageInfo;
}
@@ -779,7 +771,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
pw.println(" WebView packages:");
for (WebViewProviderInfo provider : allProviders) {
List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+ mSystemInterface.getPackageInfoForProviderAllUsers(provider);
PackageInfo systemUserPackageInfo =
userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
if (systemUserPackageInfo == null) {
@@ -798,8 +790,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
if (validity == VALIDITY_OK) {
boolean installedForAllUsers =
isInstalledAndEnabledForAllUsers(
- mSystemInterface.getPackageInfoForProviderAllUsers(
- mContext, provider));
+ mSystemInterface.getPackageInfoForProviderAllUsers(provider));
pw.println(
TextUtils.formatSimple(
" Valid package %s (%s) is %s installed/enabled for all users",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fa6ac651a059..516fc656ccb4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,7 +53,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -336,7 +335,6 @@ import android.service.contentcapture.ActivityEvent;
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -2867,7 +2865,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mStartingData != null) {
if (mStartingData.mAssociatedTask != null) {
// The snapshot type may have called associateStartingDataWithTask().
- attachStartingSurfaceToAssociatedTask();
+ // If this activity is rotated, don't attach to task to preserve the transform.
+ if (!hasFixedRotationTransform()) {
+ attachStartingSurfaceToAssociatedTask();
+ }
} else if (isEmbedded()) {
associateStartingWindowWithTaskIfNeeded();
}
@@ -2898,6 +2899,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|| mStartingData.mAssociatedTask != null) {
return;
}
+ if (task.isVisible() && !task.inTransition()) {
+ // Don't associated with task if the task is visible especially when the activity is
+ // embedded. We just need to show splash screen on the activity in case the first frame
+ // is not ready.
+ return;
+ }
associateStartingDataWithTask();
attachStartingSurfaceToAssociatedTask();
}
@@ -8639,7 +8646,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
- applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+ applySizeOverrideIfNeeded(
+ mDisplayContent,
+ info.applicationInfo,
+ newParentConfiguration,
+ resolvedConfig,
+ mOptOutEdgeToEdge,
+ hasFixedRotationTransform(),
+ getCompatDisplayInsets() != null);
mResolveConfigHint.resetTmpOverrides();
logAppCompatState();
@@ -8649,100 +8663,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
}
- /**
- * If necessary, override configuration fields related to app bounds.
- * This will happen when the app is targeting SDK earlier than 35.
- * The insets and configuration has decoupled since SDK level 35, to make the system
- * compatible to existing apps, override the configuration with legacy metrics. In legacy
- * metrics, fields such as appBounds will exclude some of the system bar areas.
- * The override contains all potentially affected fields in Configuration, including
- * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
- * All overrides to those fields should be in this method.
- *
- * TODO: Consider integrate this with computeConfigByResolveHint()
- */
- private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
- int parentWindowingMode, Configuration inOutConfig) {
- if (mDisplayContent == null) {
- return;
- }
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- int rotation = newParentConfiguration.windowConfiguration.getRotation();
- if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
- rotation = mDisplayContent.getRotation();
- }
- if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
- || getCompatDisplayInsets() != null
- || (isFloating(parentWindowingMode)
- // Check the requested windowing mode of activity as well in case it is
- // switching between PiP and fullscreen.
- && (inOutConfig.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_UNDEFINED
- || isFloating(inOutConfig.windowConfiguration.getWindowingMode())))
- || rotation == ROTATION_UNDEFINED)) {
- // If the insets configuration decoupled logic is not enabled for the app, or the app
- // already has a compat override, or the context doesn't contain enough info to
- // calculate the override, skip the override.
- return;
- }
- // Make sure the orientation related fields will be updated by the override insets, because
- // fixed rotation has assigned the fields from display's configuration.
- if (hasFixedRotationTransform()) {
- inOutConfig.windowConfiguration.setAppBounds(null);
- inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
- inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.orientation = ORIENTATION_UNDEFINED;
- }
-
- // Override starts here.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
- : mDisplayContent.mBaseDisplayWidth;
- final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
- : mDisplayContent.mBaseDisplayHeight;
- final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
- // This should be the only place override the configuration for ActivityRecord. Override
- // the value if not calculated yet.
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(nonDecorInsets);
- }
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = newParentConfiguration.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
- }
- if (inOutConfig.smallestScreenWidthDp
- == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
- && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // For the case of PIP transition and multi-window environment, the
- // smallestScreenWidthDp is handled already. Override only if the app is in
- // fullscreen.
- final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
- mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
- mDisplayContent.getDisplayMetrics().density,
- inOutConfig, true /* overrideConfig */);
- }
-
- // It's possible that screen size will be considered in different orientation with or
- // without considering the system bar insets. Override orientation as well.
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation =
- (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
- }
-
private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@NonNull Configuration parentConfig) {
task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 3b0b727597a5..26a6b00254d3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -584,7 +584,7 @@ public abstract class ActivityTaskManagerInternal {
public abstract void clearLockedTasks(String reason);
public abstract void updateUserConfiguration();
- public abstract boolean canShowErrorDialogs();
+ public abstract boolean canShowErrorDialogs(int userId);
public abstract void setProfileApp(String profileApp);
public abstract void setProfileProc(WindowProcessController wpc);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ff46b33571f3..a84598dd73dc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -190,6 +190,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -4899,14 +4900,21 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* dialog / global actions also might want different behaviors.
*/
private void updateShouldShowDialogsLocked(Configuration config) {
+ mShowDialogs = shouldShowDialogs(config, /* checkUiMode= */ true);
+ }
+
+ private boolean shouldShowDialogs(Configuration config, boolean checkUiMode) {
final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
&& config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
&& config.navigation == Configuration.NAVIGATION_NONAV);
final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
HIDE_ERROR_DIALOGS, 0) != 0;
- mShowDialogs = inputMethodExists
- && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config)
- && !hideDialogsSet;
+ boolean showDialogs = inputMethodExists && !hideDialogsSet;
+ if (checkUiMode) {
+ showDialogs = showDialogs
+ && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config);
+ }
+ return showDialogs;
}
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
@@ -7148,15 +7156,67 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public boolean canShowErrorDialogs() {
+ public boolean canShowErrorDialogs(int userId) {
synchronized (mGlobalLock) {
- return mShowDialogs && !mSleeping && !mShuttingDown
+ final boolean showDialogs = mShowDialogs
+ || shouldShowDialogsForVisibleBackgroundUserLocked(userId);
+ final UserInfo userInfo = getUserManager().getUserInfo(userId);
+ if (userInfo == null) {
+ // Unable to retrieve user information. Returning false, assuming there is
+ // no valid user with the given id.
+ return false;
+ }
+ return showDialogs && !mSleeping && !mShuttingDown
&& !mKeyguardController.isKeyguardOrAodShowing(DEFAULT_DISPLAY)
- && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
- mAmInternal.getCurrentUserId())
+ && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, userId)
&& !(UserManager.isDeviceInDemoMode(mContext)
- && mAmInternal.getCurrentUser().isDemo());
+ && userInfo.isDemo());
+ }
+ }
+
+ /**
+ * Checks if the given user is a visible background user, which is a full, background user
+ * assigned to secondary displays on the devices that have
+ * {@link UserManager#isVisibleBackgroundUsersEnabled()
+ * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
+ *
+ * @see UserManager#isUserVisible()
+ */
+ private boolean isVisibleBackgroundUser(int userId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return false;
+ }
+ boolean isForeground = getCurrentUserId() == userId;
+ boolean isProfile = getUserManager().isProfile(userId);
+ boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId);
+ return isVisible && !isForeground && !isProfile;
+ }
+
+ /**
+ * In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to
+ * {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked}
+ * because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are
+ * not displayed when an ANR or a crash occurs. However, in the automotive multi-user
+ * multi-display environment, this can confuse the passenger users and leave them
+ * uninformed when an app is terminated by the ANR or crash without any notification.
+ * To address this, error dialogs are allowed for the passenger users who have UI access
+ * on assigned displays (a.k.a. visible background users) on devices that have
+ * config_multiuserVisibleBackgroundUsers enabled even though the UI mode is
+ * {@link Configuration#UI_MODE_TYPE_CAR}.
+ *
+ * @see ActivityTaskManagerService#updateShouldShowDialogsLocked
+ */
+ private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) {
+ if (!isVisibleBackgroundUser(userId)) {
+ return false;
+ }
+ final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId);
+ final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
+ if (dc == null) {
+ return false;
}
+ return shouldShowDialogs(dc.getConfiguration(), /* checkUiMode= */ false);
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index d9f11b1635cc..05d4c821c161 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -226,6 +226,14 @@ class AppCompatAspectRatioOverrides {
: getDefaultMinAspectRatio();
}
+ float getDefaultMinAspectRatioForUnresizableAppsFromConfig() {
+ return mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps();
+ }
+
+ boolean isSplitScreenAspectRatioForUnresizableAppsEnabled() {
+ return mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ }
+
private float getDisplaySizeMinAspectRatio() {
final DisplayArea displayArea = mActivityRecord.getDisplayArea();
if (displayArea == null) {
@@ -278,7 +286,7 @@ class AppCompatAspectRatioOverrides {
return getSplitScreenAspectRatio();
}
- private float getDefaultMinAspectRatio() {
+ float getDefaultMinAspectRatio() {
if (mActivityRecord.getDisplayArea() == null
|| !mAppCompatConfiguration
.getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 8421765060ce..0f8d68b713a7 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -166,15 +166,14 @@ class BackNavigationController {
return null;
}
- // Move focus to the top embedded window if possible
- if (mWindowManagerService.moveFocusToAdjacentEmbeddedWindow(window)) {
- window = wmService.getFocusedWindowLocked();
- if (window == null) {
- Slog.e(TAG, "New focused window is null, returning null.");
- return null;
- }
+ // Updating the window to the most recently used one among the embedded windows
+ // that are displayed adjacently, unless the IME is visible.
+ // When the IME is visible, the IME is displayed on top of embedded activities.
+ // In that case, the back event should still be delivered to focused activity in
+ // order to dismiss the IME.
+ if (!window.getDisplayContent().getImeContainer().isVisible()) {
+ window = mWindowManagerService.getMostRecentUsedEmbeddedWindowForBack(window);
}
-
if (!window.isDrawn()) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Focused window didn't have a valid surface drawn.");
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index efd52026cfec..3ebaf03c4a31 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -22,14 +22,23 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
@@ -38,11 +47,14 @@ import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGU
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.LocaleList;
+import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -173,6 +185,110 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
}
+ /**
+ * If necessary, override configuration fields related to app bounds.
+ * This will happen when the app is targeting SDK earlier than 35.
+ * The insets and configuration has decoupled since SDK level 35, to make the system
+ * compatible to existing apps, override the configuration with legacy metrics. In legacy
+ * metrics, fields such as appBounds will exclude some of the system bar areas.
+ * The override contains all potentially affected fields in Configuration, including
+ * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+ * All overrides to those fields should be in this method.
+ *
+ * TODO: Consider integrate this with computeConfigByResolveHint()
+ */
+ static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
+ Configuration newParentConfiguration, Configuration inOutConfig,
+ boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform,
+ boolean hasCompatDisplayInsets) {
+ if (displayContent == null) {
+ return;
+ }
+ final boolean useOverrideInsetsForConfig =
+ displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration
+ ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ && !appInfo.isChangeEnabled(
+ OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
+ : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
+ final int parentWindowingMode =
+ newParentConfiguration.windowConfiguration.getWindowingMode();
+ final boolean isFloating = isFloating(parentWindowingMode)
+ // Check the requested windowing mode of activity as well in case it is
+ // switching between PiP and fullscreen.
+ && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
+ || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
+ rotation = displayContent.getRotation();
+ }
+ if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig
+ || hasCompatDisplayInsets
+ || isFloating
+ || rotation == ROTATION_UNDEFINED)) {
+ // If the insets configuration decoupled logic is not enabled for the app, or the app
+ // already has a compat override, or the context doesn't contain enough info to
+ // calculate the override, skip the override.
+ return;
+ }
+ // Make sure the orientation related fields will be updated by the override insets, because
+ // fixed rotation has assigned the fields from display's configuration.
+ if (hasFixedRotationTransform) {
+ inOutConfig.windowConfiguration.setAppBounds(null);
+ inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.orientation = ORIENTATION_UNDEFINED;
+ }
+
+ // Override starts here.
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated
+ ? displayContent.mBaseDisplayHeight
+ : displayContent.mBaseDisplayWidth;
+ final int dh = rotated
+ ? displayContent.mBaseDisplayWidth
+ : displayContent.mBaseDisplayHeight;
+ // This should be the only place override the configuration for ActivityRecord. Override
+ // the value if not calculated yet.
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(
+ newParentConfiguration.windowConfiguration.getBounds());
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outAppBounds.inset(displayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+ }
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = newParentConfiguration.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+ }
+ if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // For the case of PIP transition and multi-window environment, the
+ // smallestScreenWidthDp is handled already. Override only if the app is in
+ // fullscreen.
+ final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
+ displayContent.computeSizeRanges(info, rotated, dw, dh,
+ displayContent.getDisplayMetrics().density,
+ inOutConfig, true /* overrideConfig */);
+ }
+
+ // It's possible that screen size will be considered in different orientation with or
+ // without considering the system bar insets. Override orientation as well.
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT
+ : ORIENTATION_LANDSCAPE;
+ }
+ }
+
/** Returns {@code true} if requested override override configuration is not empty. */
boolean hasRequestedOverrideConfiguration() {
return mHasOverrideConfiguration;
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index f9f5058415c6..3ecdff6b18a0 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -16,18 +16,33 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
+import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.AppCompatTaskInfo;
+import android.app.TaskInfo;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.Size;
import android.view.Gravity;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+
import java.util.function.Consumer;
/**
@@ -38,6 +53,8 @@ public final class DesktopModeBoundsCalculator {
public static final float DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties
.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f;
+ public static final int DESKTOP_MODE_LANDSCAPE_APP_PADDING = SystemProperties
+ .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25);
/**
* Updates launch bounds for an activity with respect to its activity options, window layout,
@@ -48,12 +65,8 @@ public final class DesktopModeBoundsCalculator {
@NonNull Rect outBounds, @NonNull Consumer<String> logger) {
// Use stable frame instead of raw frame to avoid launching freeform windows on top of
// stable insets, which usually are system widgets such as sysbar & navbar.
- final TaskDisplayArea displayArea = task.getDisplayArea();
- final Rect screenBounds = displayArea.getBounds();
final Rect stableBounds = new Rect();
- displayArea.getStableRect(stableBounds);
- final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ task.getDisplayArea().getStableRect(stableBounds);
if (options != null && options.getLaunchBounds() != null) {
outBounds.set(options.getLaunchBounds());
@@ -63,37 +76,282 @@ public final class DesktopModeBoundsCalculator {
final int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if (layout.hasSpecifiedSize()) {
calculateLayoutBounds(stableBounds, layout, outBounds,
- new Size(desiredWidth, desiredHeight));
+ calculateIdealSize(stableBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
stableBounds);
logger.accept("layout specifies sizes, inheriting size and applying gravity");
} else if (verticalGravity > 0 || horizontalGravity > 0) {
- calculateAndCentreInitialBounds(outBounds, screenBounds);
+ outBounds.set(calculateInitialBounds(task, activity, stableBounds));
applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
stableBounds);
logger.accept("layout specifies gravity, applying desired bounds and gravity");
}
} else {
- calculateAndCentreInitialBounds(outBounds, screenBounds);
+ outBounds.set(calculateInitialBounds(task, activity, stableBounds));
logger.accept("layout not specified, applying desired bounds");
}
}
/**
- * Calculates the initial height and width of a task in desktop mode and centers it within the
- * window bounds.
+ * Calculates the initial bounds required for an application to fill a scale of the display
+ * bounds without any letterboxing. This is done by taking into account the applications
+ * fullscreen size, aspect ratio, orientation and resizability to calculate an area this is
+ * compatible with the applications previous configuration.
+ */
+ private static @NonNull Rect calculateInitialBounds(@NonNull Task task,
+ @NonNull ActivityRecord activity, @NonNull Rect stableBounds
+ ) {
+ final TaskInfo taskInfo = task.getTaskInfo();
+ // Display bounds not taking into account insets.
+ final TaskDisplayArea displayArea = task.getDisplayArea();
+ final Rect screenBounds = displayArea.getBounds();
+ final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ if (!Flags.enableWindowingDynamicInitialBounds()) {
+ return centerInScreen(idealSize, screenBounds);
+ }
+ // TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
+ float appAspectRatio = calculateAspectRatio(task, activity);
+ final float tdaWidth = stableBounds.width();
+ final float tdaHeight = stableBounds.height();
+ final int activityOrientation = activity.getOverrideOrientation();
+ final Size initialSize = switch (taskInfo.configuration.orientation) {
+ case ORIENTATION_LANDSCAPE -> {
+ // Device in landscape orientation.
+ if (appAspectRatio == 0) {
+ appAspectRatio = 1;
+ }
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationPortrait(activityOrientation)) {
+ // For portrait resizeable activities, respect apps fullscreen width but
+ // apply ideal size height.
+ yield new Size((int) ((tdaHeight / appAspectRatio) + 0.5f),
+ idealSize.getHeight());
+ }
+ // For landscape resizeable activities, simply apply ideal size.
+ yield idealSize;
+ }
+ // If activity is unresizeable, regardless of orientation, calculate maximum size
+ // (within the ideal size) maintaining original aspect ratio.
+ yield maximizeSizeGivenAspectRatio(
+ activity.getOverrideOrientation(), idealSize, appAspectRatio);
+ }
+ case ORIENTATION_PORTRAIT -> {
+ // Device in portrait orientation.
+ final int customPortraitWidthForLandscapeApp = screenBounds.width()
+ - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationLandscape(activityOrientation)) {
+ if (appAspectRatio == 0) {
+ appAspectRatio = tdaWidth / (tdaWidth - 1);
+ }
+ // For landscape resizeable activities, respect apps fullscreen height and
+ // apply custom app width.
+ yield new Size(customPortraitWidthForLandscapeApp,
+ (int) ((tdaWidth / appAspectRatio) + 0.5f));
+ }
+ // For portrait resizeable activities, simply apply ideal size.
+ yield idealSize;
+ }
+ if (appAspectRatio == 0) {
+ appAspectRatio = 1;
+ }
+ if (isFixedOrientationLandscape(activityOrientation)) {
+ // For landscape unresizeable activities, apply custom app width to ideal size
+ // and calculate maximum size with this area while maintaining original aspect
+ // ratio.
+ yield maximizeSizeGivenAspectRatio(activityOrientation,
+ new Size(customPortraitWidthForLandscapeApp, idealSize.getHeight()),
+ appAspectRatio);
+ }
+ // For portrait unresizeable activities, calculate maximum size (within the ideal
+ // size) maintaining original aspect ratio.
+ yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio);
+ }
+ default -> idealSize;
+ };
+ return centerInScreen(initialSize, screenBounds);
+ }
+
+ /**
+ * Calculates the largest size that can fit in a given area while maintaining a specific aspect
+ * ratio.
+ */
+ private static @NonNull Size maximizeSizeGivenAspectRatio(
+ @ActivityInfo.ScreenOrientation int orientation,
+ @NonNull Size targetArea,
+ float aspectRatio
+ ) {
+ final int targetHeight = targetArea.getHeight();
+ final int targetWidth = targetArea.getWidth();
+ final int finalHeight;
+ final int finalWidth;
+ if (isFixedOrientationPortrait(orientation)) {
+ // Portrait activity.
+ // Calculate required width given ideal height and aspect ratio.
+ int tempWidth = (int) (targetHeight / aspectRatio);
+ if (tempWidth <= targetWidth) {
+ // If the calculated width does not exceed the ideal width, overall size is within
+ // ideal size and can be applied.
+ finalHeight = targetHeight;
+ finalWidth = tempWidth;
+ } else {
+ // Applying target height cause overall size to exceed ideal size when maintain
+ // aspect ratio. Instead apply ideal width and calculate required height to respect
+ // aspect ratio.
+ finalWidth = targetWidth;
+ finalHeight = (int) (finalWidth * aspectRatio);
+ }
+ } else {
+ // Landscape activity.
+ // Calculate required width given ideal height and aspect ratio.
+ int tempWidth = (int) (targetHeight * aspectRatio);
+ if (tempWidth <= targetWidth) {
+ // If the calculated width does not exceed the ideal width, overall size is within
+ // ideal size and can be applied.
+ finalHeight = targetHeight;
+ finalWidth = tempWidth;
+ } else {
+ // Applying target height cause overall size to exceed ideal size when maintain
+ // aspect ratio. Instead apply ideal width and calculate required height to respect
+ // aspect ratio.
+ finalWidth = targetWidth;
+ finalHeight = (int) (finalWidth / aspectRatio);
+ }
+ }
+ return new Size(finalWidth, finalHeight);
+ }
+
+ /**
+ * Calculates the aspect ratio of an activity from its fullscreen bounds.
+ */
+ @VisibleForTesting
+ static float calculateAspectRatio(@NonNull Task task, @NonNull ActivityRecord activity) {
+ final TaskInfo taskInfo = task.getTaskInfo();
+ final float fullscreenWidth = task.getDisplayArea().getBounds().width();
+ final float fullscreenHeight = task.getDisplayArea().getBounds().height();
+ final float maxAspectRatio = activity.getMaxAspectRatio();
+ final float minAspectRatio = activity.getMinAspectRatio();
+ float desiredAspectRatio = 0;
+ if (taskInfo.isRunning) {
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ if (appCompatTaskInfo.topActivityBoundsLetterboxed) {
+ desiredAspectRatio = (float) Math.max(
+ appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight)
+ / Math.min(appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight);
+ } else {
+ desiredAspectRatio = Math.max(fullscreenHeight, fullscreenWidth)
+ / Math.min(fullscreenHeight, fullscreenWidth);
+ }
+ } else {
+ final float letterboxAspectRatioOverride =
+ getFixedOrientationLetterboxAspectRatio(activity, task);
+ if (!task.mDisplayContent.getIgnoreOrientationRequest()) {
+ desiredAspectRatio = DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+ } else if (letterboxAspectRatioOverride
+ > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+ desiredAspectRatio = letterboxAspectRatioOverride;
+ }
+ }
+ // If the activity matches display orientation, the display aspect ratio should be used
+ if (activityMatchesDisplayOrientation(
+ taskInfo.configuration.orientation,
+ activity.getOverrideOrientation())) {
+ desiredAspectRatio = Math.max(fullscreenWidth, fullscreenHeight)
+ / Math.min(fullscreenWidth, fullscreenHeight);
+ }
+ if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
+ desiredAspectRatio = maxAspectRatio;
+ } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
+ desiredAspectRatio = minAspectRatio;
+ }
+ return desiredAspectRatio;
+ }
+
+ private static boolean activityMatchesDisplayOrientation(
+ @Configuration.Orientation int deviceOrientation,
+ @ActivityInfo.ScreenOrientation int activityOrientation) {
+ if (deviceOrientation == ORIENTATION_PORTRAIT) {
+ return isFixedOrientationPortrait(activityOrientation);
+ }
+ return isFixedOrientationLandscape(activityOrientation);
+ }
+
+ /**
+ * Calculates the desired initial bounds for applications in desktop windowing. This is done as
+ * a scale of the screen bounds.
+ */
+ private static @NonNull Size calculateIdealSize(@NonNull Rect screenBounds, float scale) {
+ final int width = (int) (screenBounds.width() * scale);
+ final int height = (int) (screenBounds.height() * scale);
+ return new Size(width, height);
+ }
+
+ /**
+ * Adjusts bounds to be positioned in the middle of the screen.
*/
- private static void calculateAndCentreInitialBounds(@NonNull Rect outBounds,
+ private static @NonNull Rect centerInScreen(@NonNull Size desiredSize,
@NonNull Rect screenBounds) {
- // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- // The desired dimensions that a fully resizable window should take when initially entering
- // desktop mode. Calculated as a percentage of the available display area as defined by the
- // DESKTOP_MODE_INITIAL_BOUNDS_SCALE.
- final int desiredWidth = (int) (screenBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- final int desiredHeight = (int) (screenBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- outBounds.right = desiredWidth;
- outBounds.bottom = desiredHeight;
- outBounds.offset(screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY());
+ // TODO(b/325240051): Position apps with bottom heavy offset
+ final int heightOffset = (screenBounds.height() - desiredSize.getHeight()) / 2;
+ final int widthOffset = (screenBounds.width() - desiredSize.getWidth()) / 2;
+ final Rect resultBounds = new Rect(0, 0,
+ desiredSize.getWidth(), desiredSize.getHeight());
+ resultBounds.offset(screenBounds.left + widthOffset, screenBounds.top + heightOffset);
+ return resultBounds;
+ }
+
+ private static float getFixedOrientationLetterboxAspectRatio(@NonNull ActivityRecord activity,
+ @NonNull Task task) {
+ return activity.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps(activity, task)
+ : activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .getDefaultMinAspectRatio();
+ }
+
+ private static float getDefaultMinAspectRatioForUnresizableApps(
+ @NonNull ActivityRecord activity,
+ @NonNull Task task) {
+ final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
+ activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ if (appCompatAspectRatioOverrides.isSplitScreenAspectRatioForUnresizableAppsEnabled()) {
+ // Default letterbox aspect ratio for unresizable apps.
+ return getSplitScreenAspectRatio(activity, task);
+ }
+
+ if (appCompatAspectRatioOverrides.getDefaultMinAspectRatioForUnresizableAppsFromConfig()
+ > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+ return appCompatAspectRatioOverrides
+ .getDefaultMinAspectRatioForUnresizableAppsFromConfig();
+ }
+
+ return appCompatAspectRatioOverrides.getDefaultMinAspectRatio();
+ }
+
+ /**
+ * Calculates the aspect ratio of the available display area when an app enters split-screen on
+ * a given device, taking into account any dividers and insets.
+ */
+ private static float getSplitScreenAspectRatio(@NonNull ActivityRecord activity,
+ @NonNull Task task) {
+ final int dividerWindowWidth =
+ activity.mWmService.mContext.getResources().getDimensionPixelSize(
+ R.dimen.docked_stack_divider_thickness);
+ final int dividerInsets =
+ activity.mWmService.mContext.getResources().getDimensionPixelSize(
+ R.dimen.docked_stack_divider_insets);
+ final int dividerSize = dividerWindowWidth - dividerInsets * 2;
+ final Rect bounds = new Rect(0, 0,
+ task.mDisplayContent.getDisplayInfo().appWidth,
+ task.mDisplayContent.getDisplayInfo().appHeight);
+ if (bounds.width() >= bounds.height()) {
+ bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
+ bounds.right = bounds.centerX();
+ } else {
+ bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
+ bounds.bottom = bounds.centerY();
+ }
+ return computeAspectRatio(bounds);
}
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index aacd3c65154f..548addbef39d 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -25,7 +25,6 @@ import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.os.SystemProperties;
import android.util.Slog;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
@@ -38,19 +37,9 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
private static final boolean DEBUG = false;
- public static final float DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
- SystemProperties
- .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f;
-
- /**
- * Flag to indicate whether to restrict desktop mode to supported devices.
- */
- private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
-
private StringBuilder mLogBuilder;
- private final Context mContext;
+ @NonNull private final Context mContext;
DesktopModeLaunchParamsModifier(@NonNull Context context) {
mContext = context;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 93711497f590..fa603682bc40 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3895,6 +3895,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
+ * Returns the focused window of the given Activity if the Activity is focused.
+ */
+ WindowState findFocusedWindow(ActivityRecord activityRecord) {
+ final ActivityRecord tmpApp = mFocusedApp;
+ mTmpWindow = null;
+ try {
+ mFocusedApp = activityRecord;
+ // mFindFocusedWindow will populate mTmpWindow with the new focused window when found.
+ activityRecord.forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
+ } finally {
+ mFocusedApp = tmpApp;
+ }
+ return mTmpWindow;
+ }
+
+ /**
* Update the focused window and make some adjustments if the focus has changed.
*
* @param mode Indicates the situation we are in. Possible modes are:
@@ -6911,6 +6927,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Whether {@link #mAnimatingRecents} is going to be the top activity. */
private boolean mRecentsWillBeTop;
+ FixedRotationTransitionListener() {
+ super(DisplayContent.this.mDisplayId);
+ }
+
/**
* If the recents activity has a fixed orientation which is different from the current top
* activity, it will be rotated before being shown so we avoid a screen rotation animation
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 80362a44a33f..c3339cde6828 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -588,7 +588,7 @@ public class DisplayPolicy {
gesturesPointerEventCallbacks);
displayContent.registerPointerEventListener(mSystemGestures);
}
- mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() {
+ mAppTransitionListener = new WindowManagerInternal.AppTransitionListener(displayId) {
private Runnable mAppTransitionPending = () -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 63fe94c33061..e50a089a4d5e 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -44,6 +44,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.server.UiThread;
+import com.android.window.flags.Flags;
/**
* Controls camera compatibility treatment that handles orientation mismatch between camera
@@ -69,6 +70,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
@NonNull
private final ActivityRefresher mActivityRefresher;
+ @Nullable
+ private Task mCameraTask;
+
@ScreenOrientation
private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
@@ -104,7 +108,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
* guaranteed to match, the rotation can cause letterboxing.
*
* <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
- * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
+ * #isTreatmentEnabledForDisplay} for conditions enabling the treatment.
*/
@ScreenOrientation
int getOrientation() {
@@ -136,9 +140,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
// are aligned when they compute orientation of the preview.
// This means that even for a landscape-only activity and a device with landscape natural
// orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
- // natural orientation = portrait window = portait camera is the main wrong assumption
+ // natural orientation = portrait window = portrait camera is the main wrong assumption
// that apps make when they implement camera previews so landscape windows need be
- // rotated in the orientation oposite to the natural one even if it's portrait.
+ // rotated in the orientation opposite to the natural one even if it's portrait.
// TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
// of the portrait and landscape orientation requests.
final int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
@@ -296,6 +300,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
@Override
public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
@NonNull String cameraId) {
+ mCameraTask = cameraActivity.getTask();
// Checking whether an activity in fullscreen rather than the task as this camera
// compat treatment doesn't cover activity embedding.
if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -305,7 +310,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
// Checking that the whole app is in multi-window mode as we shouldn't show toast
// for the activity embedding case.
- if (cameraActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ if (mCameraTask != null && mCameraTask.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
&& isTreatmentEnabledForActivity(
cameraActivity, /* mustBeFullscreen */ false)) {
final PackageManager packageManager = mWmService.mContext.getPackageManager();
@@ -343,10 +348,15 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
@Override
public boolean onCameraClosed(@NonNull String cameraId) {
- // Top activity in the same task as the camera activity, or `null` if the task is
- // closed.
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
+ final ActivityRecord topActivity;
+ if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
+ topActivity = mCameraTask != null ? mCameraTask.getTopActivity(
+ /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
+ } else {
+ topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
+ }
+
+ mCameraTask = null;
if (topActivity == null) {
return true;
}
@@ -368,8 +378,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
mDisplayContent.mDisplayId);
// Checking whether an activity in fullscreen rather than the task as this camera compat
// treatment doesn't cover activity embedding.
- // TODO(b/350495350): Consider checking whether this activity is the camera activity, or
- // whether the top activity has the same task as the one which opened camera.
if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 348384203fba..dcadb0f31085 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -397,9 +397,11 @@ class InsetsStateController {
onRequestedVisibleTypesChanged(newControlTargets.valueAt(i));
}
newControlTargets.clear();
- // Check for and try to run the scheduled show IME request (if it exists), as we
- // now applied the surface transaction and notified the target of the new control.
- getImeSourceProvider().checkAndStartShowImePostLayout();
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ // Check for and try to run the scheduled show IME request (if it exists), as we
+ // now applied the surface transaction and notified the target of the new control.
+ getImeSourceProvider().checkAndStartShowImePostLayout();
+ }
});
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index c26684f60731..cc95518cce39 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -39,7 +39,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
@@ -537,27 +536,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public boolean startMovingTask(IWindow window, float startX, float startY) {
- if (DEBUG_TASK_POSITIONING) Slog.d(
- TAG_WM, "startMovingTask: {" + startX + "," + startY + "}");
-
- final long ident = Binder.clearCallingIdentity();
- try {
- return mService.mTaskPositioningController.startMovingTask(window, startX, startY);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ return false;
}
@Override
public void finishMovingTask(IWindow window) {
- if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishMovingTask");
-
- final long ident = Binder.clearCallingIdentity();
- try {
- mService.mTaskPositioningController.finishTaskPositioning(window);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
deleted file mode 100644
index 972dd2e382cc..000000000000
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- * 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.server.wm;
-
-import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
-import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED;
-import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
-import android.annotation.NonNull;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.InputConfig;
-import android.os.RemoteException;
-import android.os.Trace;
-import android.util.DisplayMetrics;
-import android.util.Slog;
-import android.view.BatchedInputEventReceiver;
-import android.view.InputApplicationHandle;
-import android.view.InputChannel;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.InputWindowHandle;
-import android.view.MotionEvent;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.TaskResizingAlgorithm;
-import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
-import com.android.internal.protolog.ProtoLog;
-
-import java.util.concurrent.CompletableFuture;
-
-class TaskPositioner implements IBinder.DeathRecipient {
- private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
- private static final String TAG_LOCAL = "TaskPositioner";
- private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
- private static Factory sFactory;
-
- public static final float RESIZING_HINT_ALPHA = 0.5f;
-
- public static final int RESIZING_HINT_DURATION_MS = 0;
-
- private final WindowManagerService mService;
- private InputEventReceiver mInputEventReceiver;
- private DisplayContent mDisplayContent;
- private Rect mTmpRect = new Rect();
- private int mMinVisibleWidth;
- private int mMinVisibleHeight;
-
- @VisibleForTesting
- Task mTask;
- WindowState mWindow;
- private boolean mResizing;
- private boolean mPreserveOrientation;
- private boolean mStartOrientationWasLandscape;
- private final Rect mWindowOriginalBounds = new Rect();
- private final Rect mWindowDragBounds = new Rect();
- private final Point mMaxVisibleSize = new Point();
- private float mStartDragX;
- private float mStartDragY;
- @CtrlType
- private int mCtrlType = CTRL_NONE;
- @VisibleForTesting
- boolean mDragEnded;
- IBinder mClientCallback;
-
- InputChannel mClientChannel;
- InputApplicationHandle mDragApplicationHandle;
- InputWindowHandle mDragWindowHandle;
-
- /** Use {@link #create(WindowManagerService)} instead. */
- @VisibleForTesting
- TaskPositioner(WindowManagerService service) {
- mService = service;
- }
-
- private boolean onInputEvent(InputEvent event) {
- // All returns need to be in the try block to make sure the finishInputEvent is
- // called correctly.
- if (!(event instanceof MotionEvent)
- || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
- return false;
- }
- final MotionEvent motionEvent = (MotionEvent) event;
- if (mDragEnded) {
- // The drag has ended but the clean-up message has not been processed by
- // window manager. Drop events that occur after this until window manager
- // has a chance to clean-up the input handle.
- return true;
- }
-
- final float newX = motionEvent.getRawX();
- final float newY = motionEvent.getRawY();
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- if (DEBUG_TASK_POSITIONING) {
- Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
- }
- }
- break;
-
- case MotionEvent.ACTION_MOVE: {
- if (DEBUG_TASK_POSITIONING) {
- Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
- }
- synchronized (mService.mGlobalLock) {
- mDragEnded = notifyMoveLocked(newX, newY);
- mTask.getDimBounds(mTmpRect);
- }
- if (!mTmpRect.equals(mWindowDragBounds)) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "wm.TaskPositioner.resizeTask");
- mService.mAtmService.resizeTask(
- mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- }
- break;
-
- case MotionEvent.ACTION_UP: {
- if (DEBUG_TASK_POSITIONING) {
- Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
- }
- mDragEnded = true;
- }
- break;
-
- case MotionEvent.ACTION_CANCEL: {
- if (DEBUG_TASK_POSITIONING) {
- Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
- }
- mDragEnded = true;
- }
- break;
- }
-
- if (mDragEnded) {
- final boolean wasResizing = mResizing;
- synchronized (mService.mGlobalLock) {
- endDragLocked();
- mTask.getDimBounds(mTmpRect);
- }
- if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
- // We were using fullscreen surface during resizing. Request
- // resizeTask() one last time to restore surface to window size.
- mService.mAtmService.resizeTask(
- mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
- }
-
- // Post back to WM to handle clean-ups. We still need the input
- // event handler for the last finishInputEvent()!
- mService.mTaskPositioningController.finishTaskPositioning();
- }
- return true;
- }
-
- @VisibleForTesting
- Rect getWindowDragBounds() {
- return mWindowDragBounds;
- }
-
- /**
- * @param displayContent The Display that the window being dragged is on.
- * @param win The window which will be dragged.
- */
- CompletableFuture<Void> register(DisplayContent displayContent, @NonNull WindowState win) {
- if (DEBUG_TASK_POSITIONING) {
- Slog.d(TAG, "Registering task positioner");
- }
-
- if (mClientChannel != null) {
- Slog.e(TAG, "Task positioner already registered");
- return completedFuture(null);
- }
-
- mDisplayContent = displayContent;
- mClientChannel = mService.mInputManager.createInputChannel(TAG);
-
- mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
- mClientChannel, mService.mAnimationHandler.getLooper(),
- mService.mAnimator.getChoreographer(), this::onInputEvent);
-
- mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG,
- DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
-
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
- displayContent.getDisplayId());
- mDragWindowHandle.name = TAG;
- mDragWindowHandle.token = mClientChannel.getToken();
- mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
- mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
- mDragWindowHandle.ownerPid = WindowManagerService.MY_PID;
- mDragWindowHandle.ownerUid = WindowManagerService.MY_UID;
- mDragWindowHandle.scaleFactor = 1.0f;
- // When dragging the window around, we do not want to steal focus for the window.
- mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
-
- // The drag window cannot receive new touches.
- mDragWindowHandle.touchableRegion.setEmpty();
-
- // Pause rotations before a drag.
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during re-position");
- mDisplayContent.getDisplayRotation().pause();
-
- // Notify InputMonitor to take mDragWindowHandle.
- return mService.mTaskPositioningController.showInputSurface(win.getDisplayId())
- .thenRun(() -> {
- // The global lock is held by the callers of register but released before the async
- // results are waited on. We must acquire the lock in this callback to ensure thread
- // safety.
- synchronized (mService.mGlobalLock) {
- final Rect displayBounds = mTmpRect;
- displayContent.getBounds(displayBounds);
- final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
- mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics);
- mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics);
- mMaxVisibleSize.set(displayBounds.width(), displayBounds.height());
-
- mDragEnded = false;
-
- try {
- mClientCallback = win.mClient.asBinder();
- mClientCallback.linkToDeath(this, 0 /* flags */);
- } catch (RemoteException e) {
- // The caller has died, so clean up TaskPositioningController.
- mService.mTaskPositioningController.finishTaskPositioning();
- return;
- }
- mWindow = win;
- mTask = win.getTask();
- }
- });
- }
-
- void unregister() {
- if (DEBUG_TASK_POSITIONING) {
- Slog.d(TAG, "Unregistering task positioner");
- }
-
- if (mClientChannel == null) {
- Slog.e(TAG, "Task positioner not registered");
- return;
- }
-
- mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId());
- mService.mInputManager.removeInputChannel(mClientChannel.getToken());
-
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
- mClientChannel.dispose();
- mClientChannel = null;
-
- mDragWindowHandle = null;
- mDragApplicationHandle = null;
- mDragEnded = true;
-
- // Notify InputMonitor to remove mDragWindowHandle.
- mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
-
- // Resume rotations after a drag.
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after re-position");
- mDisplayContent.getDisplayRotation().resume();
- mDisplayContent = null;
- if (mClientCallback != null) {
- mClientCallback.unlinkToDeath(this, 0 /* flags */);
- }
- mWindow = null;
- }
-
- /**
- * Starts moving or resizing the task. This method should be only called from
- * {@link TaskPositioningController#startPositioningLocked} or unit tests.
- */
- void startDrag(boolean resize, boolean preserveOrientation, float startX, float startY) {
- if (DEBUG_TASK_POSITIONING) {
- Slog.d(TAG, "startDrag: win=" + mWindow + ", resize=" + resize
- + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
- + startY + "}");
- }
- // Use the bounds of the task which accounts for
- // multiple app windows. Don't use any bounds from win itself as it
- // may not be the same size as the task.
- final Rect startBounds = mTmpRect;
- mTask.getBounds(startBounds);
-
- mCtrlType = CTRL_NONE;
- mStartDragX = startX;
- mStartDragY = startY;
- mPreserveOrientation = preserveOrientation;
-
- if (resize) {
- if (startX < startBounds.left) {
- mCtrlType |= CTRL_LEFT;
- }
- if (startX > startBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- }
- if (startY < startBounds.top) {
- mCtrlType |= CTRL_TOP;
- }
- if (startY > startBounds.bottom) {
- mCtrlType |= CTRL_BOTTOM;
- }
- mResizing = mCtrlType != CTRL_NONE;
- }
-
- // In case of !isDockedInEffect we are using the union of all task bounds. These might be
- // made up out of multiple windows which are only partially overlapping. When that happens,
- // the orientation from the window of interest to the entire stack might diverge. However
- // for now we treat them as the same.
- mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
- mWindowOriginalBounds.set(startBounds);
-
- // Notify the app that resizing has started, even though we haven't received any new
- // bounds yet. This will guarantee that the app starts the backdrop renderer before
- // configuration changes which could cause an activity restart.
- if (mResizing) {
- notifyMoveLocked(startX, startY);
-
- // The WindowPositionerEventReceiver callbacks are delivered on the same handler so this
- // initial resize is always guaranteed to happen before subsequent drag resizes.
- mService.mH.post(() -> {
- mService.mAtmService.resizeTask(
- mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
- });
- }
-
- // Make sure we always have valid drag bounds even if the drag ends before any move events
- // have been handled.
- mWindowDragBounds.set(startBounds);
- }
-
- private void endDragLocked() {
- mResizing = false;
- mTask.setDragResizing(false);
- }
-
- /** Returns true if the move operation should be ended. */
- @VisibleForTesting
- boolean notifyMoveLocked(float x, float y) {
- if (DEBUG_TASK_POSITIONING) {
- Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
- }
-
- if (mCtrlType != CTRL_NONE) {
- resizeDrag(x, y);
- mTask.setDragResizing(true);
- return false;
- }
-
- // This is a moving or scrolling operation.
- // Only allow to move in stable area so the target window won't be covered by system bar.
- // Though {@link Task#resolveOverrideConfiguration} should also avoid the case.
- mDisplayContent.getStableRect(mTmpRect);
- // The task may be put in a limited display area.
- mTmpRect.intersect(mTask.getRootTask().getParent().getBounds());
-
- int nX = (int) x;
- int nY = (int) y;
- if (!mTmpRect.contains(nX, nY)) {
- // For a moving operation we allow the pointer to go out of the stack bounds, but
- // use the clamped pointer position for the drag bounds computation.
- nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
- nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
- }
-
- updateWindowDragBounds(nX, nY, mTmpRect);
- return false;
- }
-
- /**
- * The user is drag - resizing the window.
- *
- * @param x The x coordinate of the current drag coordinate.
- * @param y the y coordinate of the current drag coordinate.
- */
- @VisibleForTesting
- void resizeDrag(float x, float y) {
- updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY,
- mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight,
- mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape));
- }
-
- private void updateDraggedBounds(Rect newBounds) {
- mWindowDragBounds.set(newBounds);
-
- checkBoundsForOrientationViolations(mWindowDragBounds);
- }
-
- /**
- * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
- *
- * @param bounds The bounds to be checked.
- */
- private void checkBoundsForOrientationViolations(Rect bounds) {
- // When using debug check that we are not violating the given constraints.
- if (DEBUG_ORIENTATION_VIOLATIONS) {
- if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
- Slog.e(TAG, "Orientation violation detected! should be "
- + (mStartOrientationWasLandscape ? "landscape" : "portrait")
- + " but is the other");
- } else {
- Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
- }
- if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
- Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
- + ", " + bounds.width() + ") Height(min,is)=("
- + mMinVisibleHeight + ", " + bounds.height() + ")");
- }
- if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
- Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
- + ", " + bounds.width() + ") Height(min,is)=("
- + mMaxVisibleSize.y + ", " + bounds.height() + ")");
- }
- }
- }
-
- private void updateWindowDragBounds(int x, int y, Rect rootTaskBounds) {
- final int offsetX = Math.round(x - mStartDragX);
- final int offsetY = Math.round(y - mStartDragY);
- mWindowDragBounds.set(mWindowOriginalBounds);
- // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
- final int maxLeft = rootTaskBounds.right - mMinVisibleWidth;
- final int minLeft = rootTaskBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
-
- // Vertically, the top mMinVisibleHeight of the window should remain visible.
- // (This assumes that the window caption bar is at the top of the window).
- final int minTop = rootTaskBounds.top;
- final int maxTop = rootTaskBounds.bottom - mMinVisibleHeight;
-
- mWindowDragBounds.offsetTo(
- Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
- Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
-
- if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
- "updateWindowDragBounds: " + mWindowDragBounds);
- }
-
- public String toShortString() {
- return TAG;
- }
-
- static void setFactory(Factory factory) {
- sFactory = factory;
- }
-
- static TaskPositioner create(WindowManagerService service) {
- if (sFactory == null) {
- sFactory = new Factory() {};
- }
-
- return sFactory.create(service);
- }
-
- @Override
- public void binderDied() {
- mService.mTaskPositioningController.finishTaskPositioning();
- }
-
- interface Factory {
- default TaskPositioner create(WindowManagerService service) {
- return new TaskPositioner(service);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
deleted file mode 100644
index 6f548ab01d74..000000000000
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
-import android.annotation.Nullable;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Slog;
-import android.view.Display;
-import android.view.IWindow;
-import android.view.InputWindowHandle;
-import android.view.SurfaceControl;
-
-import java.util.concurrent.CompletableFuture;
-
-/**
- * Controller for task positioning by drag.
- */
-class TaskPositioningController {
- private final WindowManagerService mService;
- private SurfaceControl mInputSurface;
- private DisplayContent mPositioningDisplay;
-
- private @Nullable TaskPositioner mTaskPositioner;
-
- private final Rect mTmpClipRect = new Rect();
-
- boolean isPositioningLocked() {
- return mTaskPositioner != null;
- }
-
- final SurfaceControl.Transaction mTransaction;
-
- InputWindowHandle getDragWindowHandleLocked() {
- return mTaskPositioner != null ? mTaskPositioner.mDragWindowHandle : null;
- }
-
- TaskPositioningController(WindowManagerService service) {
- mService = service;
- mTransaction = service.mTransactionFactory.get();
- }
-
- void hideInputSurface(int displayId) {
- if (mPositioningDisplay != null && mPositioningDisplay.getDisplayId() == displayId
- && mInputSurface != null) {
- mTransaction.hide(mInputSurface).apply();
- }
- }
-
- /**
- * @return a future that completes after window info is sent.
- */
- CompletableFuture<Void> showInputSurface(int displayId) {
- if (mPositioningDisplay == null || mPositioningDisplay.getDisplayId() != displayId) {
- return completedFuture(null);
- }
- final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
- if (mInputSurface == null) {
- mInputSurface = mService.makeSurfaceBuilder(dc.getSession())
- .setContainerLayer()
- .setName("Drag and Drop Input Consumer")
- .setCallsite("TaskPositioningController.showInputSurface")
- .setParent(dc.getOverlayLayer())
- .build();
- }
-
- final InputWindowHandle h = getDragWindowHandleLocked();
- if (h == null) {
- Slog.w(TAG_WM, "Drag is in progress but there is no "
- + "drag window handle.");
- return completedFuture(null);
- }
-
- final Display display = dc.getDisplay();
- final Point p = new Point();
- display.getRealSize(p);
- mTmpClipRect.set(0, 0, p.x, p.y);
-
- CompletableFuture<Void> result = new CompletableFuture<>();
- mTransaction.show(mInputSurface)
- .setInputWindowInfo(mInputSurface, h)
- .setLayer(mInputSurface, Integer.MAX_VALUE)
- .setPosition(mInputSurface, 0, 0)
- .setCrop(mInputSurface, mTmpClipRect)
- .addWindowInfosReportedListener(() -> result.complete(null))
- .apply();
- return result;
- }
-
- boolean startMovingTask(IWindow window, float startX, float startY) {
- WindowState win = null;
- CompletableFuture<Boolean> startPositioningLockedFuture;
- synchronized (mService.mGlobalLock) {
- win = mService.windowForClientLocked(null, window, false);
- startPositioningLockedFuture =
- startPositioningLocked(
- win, false /*resize*/, false /*preserveOrientation*/, startX, startY);
- }
-
- try {
- if (!startPositioningLockedFuture.get()) {
- return false;
- }
- } catch (Exception exception) {
- Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future",
- exception);
- return false;
- }
-
- synchronized (mService.mGlobalLock) {
- mService.mAtmService.setFocusedTask(win.getTask().mTaskId);
- }
- return true;
- }
-
- void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
- mService.mH.post(() -> {
- Task task;
- CompletableFuture<Boolean> startPositioningLockedFuture;
- synchronized (mService.mGlobalLock) {
- task = displayContent.findTaskForResizePoint(x, y);
- if (task == null || !task.isResizeable()) {
- // The task is not resizable, so don't do anything when the user drags the
- // the resize handles.
- return;
- }
- startPositioningLockedFuture =
- startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
- task.preserveOrientationOnResize(), x, y);
- }
-
- try {
- if (!startPositioningLockedFuture.get()) {
- return;
- }
- } catch (Exception exception) {
- Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future",
- exception);
- return;
- }
-
- synchronized (mService.mGlobalLock) {
- mService.mAtmService.setFocusedTask(task.mTaskId);
- }
- });
- }
-
- private CompletableFuture<Boolean> startPositioningLocked(WindowState win, boolean resize,
- boolean preserveOrientation, float startX, float startY) {
- if (DEBUG_TASK_POSITIONING)
- Slog.d(TAG_WM, "startPositioningLocked: "
- + "win=" + win + ", resize=" + resize + ", preserveOrientation="
- + preserveOrientation + ", {" + startX + ", " + startY + "}");
-
- if (win == null || win.mActivityRecord == null) {
- Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
- return completedFuture(false);
- }
- if (win.mInputChannel == null) {
- Slog.wtf(TAG_WM, "startPositioningLocked: " + win + " has no input channel, "
- + " probably being removed");
- return completedFuture(false);
- }
-
- final DisplayContent displayContent = win.getDisplayContent();
- if (displayContent == null) {
- Slog.w(TAG_WM, "startPositioningLocked: Invalid display content " + win);
- return completedFuture(false);
- }
- mPositioningDisplay = displayContent;
-
- mTaskPositioner = TaskPositioner.create(mService);
- return mTaskPositioner.register(displayContent, win).thenApply(unused -> {
- // The global lock is held by the callers of startPositioningLocked but released before
- // the async results are waited on. We must acquire the lock in this callback to ensure
- // thread safety.
- synchronized (mService.mGlobalLock) {
- // We need to grab the touch focus so that the touch events during the
- // resizing/scrolling are not sent to the app. 'win' is the main window
- // of the app, it may not have focus since there might be other windows
- // on top (eg. a dialog window).
- WindowState transferTouchFromWin = win;
- if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win
- && displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) {
- transferTouchFromWin = displayContent.mCurrentFocus;
- }
- if (!mService.mInputManager.transferTouchGesture(
- transferTouchFromWin.mInputChannel.getToken(),
- mTaskPositioner.mClientChannel.getToken())) {
- Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
- cleanUpTaskPositioner();
- return false;
- }
-
- mTaskPositioner.startDrag(resize, preserveOrientation, startX, startY);
- return true;
- }
- });
- }
-
- public void finishTaskPositioning(IWindow window) {
- if (mTaskPositioner != null && mTaskPositioner.mClientCallback == window.asBinder()) {
- finishTaskPositioning();
- }
- }
-
- void finishTaskPositioning() {
- // TaskPositioner attaches the InputEventReceiver to the animation thread. We need to
- // dispose the receiver on the same thread to avoid race conditions.
- mService.mAnimationHandler.post(() -> {
- if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning");
-
- synchronized (mService.mGlobalLock) {
- cleanUpTaskPositioner();
- mPositioningDisplay = null;
- }
- });
- }
-
- private void cleanUpTaskPositioner() {
- final TaskPositioner positioner = mTaskPositioner;
- if (positioner == null) {
- return;
- }
-
- // We need to assign task positioner to null first to indicate that we're finishing task
- // positioning.
- mTaskPositioner = null;
- positioner.unregister();
- }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f6a68d58ea27..65bc9a226f55 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -745,6 +745,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mController.isAnimating()) {
dc.enableHighPerfTransition(true);
}
+ mController.dispatchLegacyAppTransitionPending(dc.mDisplayId);
}
/**
@@ -1618,7 +1619,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.mTransitionTracer.logAbortedTransition(this);
// Syncengine abort will call through to onTransactionReady()
mSyncEngine.abort(mSyncId);
- mController.dispatchLegacyAppTransitionCancelled();
+ mController.dispatchLegacyAppTransitionCancelled(mTargetDisplays);
invokeTransitionEndedListeners();
}
@@ -1766,7 +1767,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
for (int i = 0; i < mTargets.size(); ++i) {
- final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea();
+ final WindowContainer<?> wc = mTargets.get(i).mContainer;
+ final WallpaperWindowToken wp = wc.asWallpaperToken();
+ if (wp != null) {
+ // If on a rotation leash, the wallpaper token surface needs to be shown explicitly
+ // because shell only gets the leash and the wallpaper token surface is not allowed
+ // to be changed by non-transition logic until the transition is finished.
+ if (Flags.ensureWallpaperInTransitions() && wp.isVisibleRequested()
+ && wp.getFixedRotationLeash() != null) {
+ transaction.show(wp.mSurfaceControl);
+ }
+ continue;
+ }
+ final DisplayArea<?> da = wc.asDisplayArea();
if (da == null) continue;
if (da.isVisibleRequested()) {
mController.mValidateDisplayVis.remove(da);
@@ -2168,14 +2181,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
&& !wallpaperIsOwnTarget(wallpaper)) {
wallpaper.setVisibleRequested(false);
}
- if (showWallpaper && Flags.ensureWallpaperInTransitions()
- && wallpaper.isVisibleRequested()
- && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) {
- // If on a rotation leash, we need to explicitly show the wallpaper surface
- // because shell only gets the leash and we don't allow non-transition logic
- // to touch the surfaces until the transition is over.
- t.show(wallpaper.getSurfaceControl());
- }
}
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f4ff404c2bff..1df251cf3225 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -42,6 +42,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import android.view.WindowManager;
import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
@@ -326,7 +327,6 @@ class TransitionController {
mCollectingTransition.startCollecting(timeoutMs);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
mCollectingTransition);
- dispatchLegacyAppTransitionPending();
}
void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@@ -1347,31 +1347,54 @@ class TransitionController {
mLegacyListeners.remove(listener);
}
- void dispatchLegacyAppTransitionPending() {
+ private static boolean shouldDispatchLegacyListener(
+ WindowManagerInternal.AppTransitionListener listener, int displayId) {
+ // INVALID_DISPLAY means that it is a global listener.
+ return listener.mDisplayId == Display.INVALID_DISPLAY || listener.mDisplayId == displayId;
+ }
+
+ void dispatchLegacyAppTransitionPending(int displayId) {
for (int i = 0; i < mLegacyListeners.size(); ++i) {
- mLegacyListeners.get(i).onAppTransitionPendingLocked();
+ final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
+ if (shouldDispatchLegacyListener(listener, displayId)) {
+ listener.onAppTransitionPendingLocked();
+ }
}
}
void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
+ final long now = SystemClock.uptimeMillis();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
- // TODO(shell-transitions): handle (un)occlude transition.
- mLegacyListeners.get(i).onAppTransitionStartingLocked(
- SystemClock.uptimeMillis() + statusBarTransitionDelay,
- AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+ final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
+ for (int j = 0; j < info.getRootCount(); ++j) {
+ final int displayId = info.getRoot(j).getDisplayId();
+ if (shouldDispatchLegacyListener(listener, displayId)) {
+ listener.onAppTransitionStartingLocked(
+ now + statusBarTransitionDelay,
+ AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+ }
+ }
}
}
void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
for (int i = 0; i < mLegacyListeners.size(); ++i) {
- mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token);
+ final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
+ if (shouldDispatchLegacyListener(listener, ar.getDisplayId())) {
+ listener.onAppTransitionFinishedLocked(ar.token);
+ }
}
}
- void dispatchLegacyAppTransitionCancelled() {
- for (int i = 0; i < mLegacyListeners.size(); ++i) {
- mLegacyListeners.get(i).onAppTransitionCancelledLocked(
- false /* keyguardGoingAwayCancelled */);
+ void dispatchLegacyAppTransitionCancelled(ArrayList<DisplayContent> targetDisplays) {
+ for (int i = 0; i < targetDisplays.size(); ++i) {
+ final int displayId = targetDisplays.get(i).mDisplayId;
+ for (int j = 0; j < mLegacyListeners.size(); ++j) {
+ final var listener = mLegacyListeners.get(j);
+ if (shouldDispatchLegacyListener(listener, displayId)) {
+ listener.onAppTransitionCancelledLocked(false /* keyguardGoingAwayCancelled */);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 42b556f77ab6..61253602c066 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -47,7 +47,6 @@ public class WindowManagerDebugConfig {
static final boolean DEBUG_LAYOUT_REPEATS = false;
static final boolean DEBUG_WINDOW_TRACE = false;
static final boolean DEBUG_TASK_MOVEMENT = false;
- static final boolean DEBUG_TASK_POSITIONING = false;
static final boolean DEBUG_ROOT_TASK = false;
static final boolean DEBUG_DISPLAY = false;
static final boolean DEBUG_POWER = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 2ea1cf88447a..132e1ee99914 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -244,6 +244,22 @@ public abstract class WindowManagerInternal {
public static abstract class AppTransitionListener {
/**
+ * The display this listener is interested in. If it is INVALID_DISPLAY, then which display
+ * should be notified depends on the dispatcher.
+ */
+ public final int mDisplayId;
+
+ /** Let transition controller decide which display should receive the callbacks. */
+ public AppTransitionListener() {
+ this(Display.INVALID_DISPLAY);
+ }
+
+ /** It will listen the transition on the given display. */
+ public AppTransitionListener(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ /**
* Called when an app transition is being setup and about to be executed.
*/
public void onAppTransitionPendingLocked() {}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index acd8b3f1dbc3..f65eea0797bf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -54,7 +54,6 @@ import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -97,6 +96,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.window.WindowProviderService.isWindowProviderService;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -1070,7 +1070,6 @@ public class WindowManagerService extends IWindowManager.Stub
/** Whether or not a layout can cause a wake up when theater mode is enabled. */
boolean mAllowTheaterModeWakeFromLayout;
- final TaskPositioningController mTaskPositioningController;
final DragDropController mDragDropController;
/** For frozen screen animations. */
@@ -1428,7 +1427,6 @@ public class WindowManagerService extends IWindowManager.Stub
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
- mTaskPositioningController = new TaskPositioningController(this);
mDragDropController = new DragDropController(this, mH.getLooper());
mHighRefreshRateDenylist = HighRefreshRateDenylist.create(context.getResources());
@@ -9379,40 +9377,82 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
- * Move focus to the adjacent embedded activity if the adjacent activity is more recently
- * created or has a window more recently added.
+ * Returns the Activity that has the most recently created window in the adjacent activities
+ * if any.
*/
- boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) {
- final TaskFragment taskFragment = focusedWindow.getTaskFragment();
+ @NonNull
+ ActivityRecord getMostRecentActivityInAdjacent(@NonNull ActivityRecord focusedActivity) {
+ final TaskFragment taskFragment = focusedActivity.getTaskFragment();
if (taskFragment == null) {
- // Skip if not an Activity window.
- return false;
+ // Return if activity no attached.
+ return focusedActivity;
}
if (!Flags.embeddedActivityBackNavFlag()) {
- // Skip if flag is not enabled.
- return false;
+ // Return if flag is not enabled.
+ return focusedActivity;
}
- if (!focusedWindow.mActivityRecord.isEmbedded()) {
- // Skip if the focused activity is not embedded
- return false;
+ if (!focusedActivity.isEmbedded()) {
+ // Return if the focused activity is not embedded.
+ return focusedActivity;
}
final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
final ActivityRecord adjacentTopActivity =
adjacentTaskFragment != null ? adjacentTaskFragment.topRunningActivity() : null;
if (adjacentTopActivity == null) {
- return false;
+ // Return if no adjacent activity.
+ return focusedActivity;
}
if (adjacentTopActivity.getLastWindowCreateTime()
- < focusedWindow.mActivityRecord.getLastWindowCreateTime()) {
- // Skip if the current focus activity has more recently active window.
+ < focusedActivity.getLastWindowCreateTime()) {
+ // Return if the current focus activity has more recently active window.
+ return focusedActivity;
+ }
+
+ return adjacentTopActivity;
+ }
+
+ @NonNull
+ WindowState getMostRecentUsedEmbeddedWindowForBack(@NonNull WindowState focusedWindow) {
+ final ActivityRecord focusedActivity = focusedWindow.getActivityRecord();
+ if (focusedActivity == null) {
+ // Not an Activity.
+ return focusedWindow;
+ }
+
+ final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent(
+ focusedActivity);
+ if (mostRecentActivityInAdjacent == focusedActivity) {
+ // Already be the most recent window.
+ return focusedWindow;
+ }
+
+ // Looks for a candidate focused window on the adjacent Activity for the back event.
+ final WindowState candidate =
+ mostRecentActivityInAdjacent.getDisplayContent().findFocusedWindow(
+ mostRecentActivityInAdjacent);
+ return candidate != null ? candidate : focusedWindow;
+ }
+
+ /**
+ * Move focus to the adjacent embedded activity if the adjacent activity is more recently
+ * created or has a window more recently added.
+ * <p>
+ * Returns {@code true} if the focused window is changed. Otherwise, returns {@code false}.
+ */
+ boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) {
+ final ActivityRecord activity = focusedWindow.getActivityRecord();
+ if (activity == null) {
return false;
}
- moveFocusToActivity(adjacentTopActivity);
+ final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent(
+ activity);
+
+ moveFocusToActivity(mostRecentActivityInAdjacent);
return !focusedWindow.isFocused();
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 60d3e787cac4..984caf1c692b 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
import static android.os.Build.VERSION_CODES.Q;
@@ -73,6 +74,7 @@ import android.os.LocaleList;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
@@ -112,6 +114,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
+ /**
+ * The max number of processes which can be top scheduling group if there are non-top visible
+ * freeform activities run in the process.
+ */
+ private static final int MAX_NUM_PERCEPTIBLE_FREEFORM =
+ SystemProperties.getInt("persist.wm.max_num_perceptible_freeform", 1);
+
private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200;
private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 3 * RAPID_ACTIVITY_LAUNCH_MS;
@@ -318,6 +327,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
public static final int ACTIVITY_STATE_FLAG_HAS_RESUMED = 1 << 21;
public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23;
+ public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24;
public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
/**
@@ -1229,6 +1239,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
ActivityRecord.State bestInvisibleState = DESTROYED;
boolean allStoppingFinishing = true;
boolean visible = false;
+ boolean hasResumedFreeform = false;
int minTaskLayer = Integer.MAX_VALUE;
int stateFlags = 0;
final boolean wasResumed = hasResumedActivity();
@@ -1256,6 +1267,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
.processPriorityPolicyForMultiWindowMode()
&& task.getAdjacentTask() != null) {
stateFlags |= ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN;
+ } else if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ hasResumedFreeform = true;
}
}
if (minTaskLayer > 0) {
@@ -1289,6 +1302,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
+ if (hasResumedFreeform
+ && com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
+ // Exclude task layer 1 because it is already the top most.
+ && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+ stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ }
stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
if (visible) {
stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
@@ -1655,6 +1674,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Otherwise if other places send wpc.getConfiguration() to client, the configuration may
// be ignored due to the seq is older.
resolvedConfig.seq = newParentConfig.seq;
+
+ if (mConfigActivityRecord != null) {
+ // Let the activity decide whether to apply the size override.
+ return;
+ }
+ final DisplayContent displayContent = mAtm.mWindowManager != null
+ ? mAtm.mWindowManager.getDefaultDisplayContentLocked()
+ : null;
+ applySizeOverrideIfNeeded(
+ displayContent,
+ mInfo,
+ newParentConfig,
+ resolvedConfig,
+ false /* optsOutEdgeToEdge */,
+ false /* hasFixedRotationTransform */,
+ false /* hasCompatDisplayInsets */);
}
void dispatchConfiguration(@NonNull Configuration config) {
@@ -2105,6 +2140,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if ((stateFlags & ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN) != 0) {
pw.print("RS|");
}
+ if ((stateFlags & ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0) {
+ pw.print("PF|");
+ }
}
} else if ((stateFlags & ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED) != 0) {
pw.print("P|");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9ebb89dfe9b6..a36cff6d7bc5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4648,14 +4648,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (!isImeLayeringTarget()) {
return false;
}
- // Note that we don't process IME window if the IME input target is not on the screen.
- // In case some unexpected IME visibility cases happen like starting the remote
- // animation on the keyguard but seeing the IME window that originally on the app
- // which behinds the keyguard.
- final WindowState imeInputTarget = getImeInputTarget();
- if (imeInputTarget != null
- && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
- return false;
+ if (!com.android.window.flags.Flags.doNotSkipImeByTargetVisibility()) {
+ // Note that we don't process IME window if the IME input target is not on the screen.
+ // In case some unexpected IME visibility cases happen like starting the remote
+ // animation on the keyguard but seeing the IME window that originally on the app
+ // which behinds the keyguard.
+ final WindowState imeInputTarget = getImeInputTarget();
+ if (imeInputTarget != null
+ && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
+ return false;
+ }
}
return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
}
@@ -5504,7 +5506,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
public SurfaceControl getAnimationLeashParent() {
- if (isStartingWindowAssociatedToTask()) {
+ if (mActivityRecord != null && !mActivityRecord.hasFixedRotationTransform()
+ && isStartingWindowAssociatedToTask()) {
return mStartingData.mAssociatedTask.mSurfaceControl;
}
return super.getAnimationLeashParent();
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 21f7eca5627a..04d5c03e64a6 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -56,7 +56,10 @@ abstract class WindowTracing {
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
Choreographer choreographer) {
- return new WindowTracingLegacy(service, choreographer);
+ if (!android.tracing.Flags.perfettoWmTracing()) {
+ return new WindowTracingLegacy(service, choreographer);
+ }
+ return new WindowTracingPerfetto(service, choreographer);
}
protected WindowTracing(WindowManagerService service, Choreographer choreographer,
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
new file mode 100644
index 000000000000..3d2c0d3f79b9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 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;
+
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+
+import android.annotation.NonNull;
+import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
+import android.internal.perfetto.protos.WindowmanagerConfig.WindowManagerConfig;
+import android.tracing.perfetto.CreateTlsStateArgs;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
+ WindowTracingDataSource.TlsState, Void> {
+ public static final String DATA_SOURCE_NAME = "android.windowmanager";
+
+ public static class TlsState {
+ public final Config mConfig;
+ public final AtomicBoolean mIsStarting = new AtomicBoolean(true);
+
+ private TlsState(Config config) {
+ mConfig = config;
+ }
+ }
+
+ public static class Config {
+ public final @WindowTraceLogLevel int mLogLevel;
+ public final boolean mLogOnFrame;
+
+ private Config(@WindowTraceLogLevel int logLevel, boolean logOnFrame) {
+ mLogLevel = logLevel;
+ mLogOnFrame = logOnFrame;
+ }
+ }
+
+ public abstract static class Instance extends DataSourceInstance {
+ public final Config mConfig;
+
+ public Instance(DataSource dataSource, int instanceIndex, Config config) {
+ super(dataSource, instanceIndex);
+ mConfig = config;
+ }
+ }
+
+ private static final Config CONFIG_DEFAULT = new Config(WindowTraceLogLevel.TRIM, true);
+ private static final int CONFIG_VALUE_UNSPECIFIED = 0;
+ private static final String TAG = "WindowTracingDataSource";
+
+ @NonNull
+ private final Consumer<Config> mOnStartCallback;
+ @NonNull
+ private final Consumer<Config> mOnStopCallback;
+
+ public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
+ @NonNull Consumer<Config> onStop) {
+ super(DATA_SOURCE_NAME);
+ mOnStartCallback = onStart;
+ mOnStopCallback = onStop;
+
+ Producer.init(InitArguments.DEFAULTS);
+ DataSourceParams params =
+ new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ .build();
+ register(params);
+ }
+
+ @Override
+ public Instance createInstance(ProtoInputStream configStream, int instanceIndex) {
+ final Config config = parseDataSourceConfig(configStream);
+
+ return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
+ @Override
+ protected void onStart(StartCallbackArguments args) {
+ mOnStartCallback.accept(mConfig);
+ }
+
+ @Override
+ protected void onStop(StopCallbackArguments args) {
+ mOnStopCallback.accept(mConfig);
+ }
+ };
+ }
+
+ @Override
+ public TlsState createTlsState(
+ CreateTlsStateArgs<Instance> args) {
+ try (Instance dsInstance = args.getDataSourceInstanceLocked()) {
+ if (dsInstance == null) {
+ // Datasource instance has been removed
+ return new TlsState(CONFIG_DEFAULT);
+ }
+ return new TlsState(dsInstance.mConfig);
+ }
+ }
+
+ private Config parseDataSourceConfig(ProtoInputStream stream) {
+ try {
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (stream.getFieldNumber() != (int) DataSourceConfig.WINDOWMANAGER_CONFIG) {
+ continue;
+ }
+ return parseWindowManagerConfig(stream);
+ }
+ Log.w(TAG, "Received start request without config parameters. Will use defaults.");
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to parse DataSourceConfig", e);
+ }
+ return null;
+ }
+
+ private Config parseWindowManagerConfig(ProtoInputStream stream) {
+ int parsedLogLevel = CONFIG_VALUE_UNSPECIFIED;
+ int parsedLogFrequency = CONFIG_VALUE_UNSPECIFIED;
+
+ try {
+ final long token = stream.start(DataSourceConfig.WINDOWMANAGER_CONFIG);
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) WindowManagerConfig.LOG_LEVEL:
+ parsedLogLevel = stream.readInt(WindowManagerConfig.LOG_LEVEL);
+ break;
+ case (int) WindowManagerConfig.LOG_FREQUENCY:
+ parsedLogFrequency = stream.readInt(WindowManagerConfig.LOG_FREQUENCY);
+ break;
+ default:
+ Log.w(TAG, "Unrecognized WindowManagerConfig field number: "
+ + stream.getFieldNumber());
+ }
+ }
+ stream.end(token);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to parse WindowManagerConfig", e);
+ }
+
+ @WindowTraceLogLevel int logLevel;
+ switch(parsedLogLevel) {
+ case CONFIG_VALUE_UNSPECIFIED:
+ Log.w(TAG, "Unspecified log level. Defaulting to TRIM");
+ logLevel = WindowTraceLogLevel.TRIM;
+ break;
+ case WindowManagerConfig.LOG_LEVEL_VERBOSE:
+ logLevel = WindowTraceLogLevel.ALL;
+ break;
+ case WindowManagerConfig.LOG_LEVEL_DEBUG:
+ logLevel = WindowTraceLogLevel.TRIM;
+ break;
+ case WindowManagerConfig.LOG_LEVEL_CRITICAL:
+ logLevel = WindowTraceLogLevel.CRITICAL;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized log level. Defaulting to TRIM");
+ logLevel = WindowTraceLogLevel.TRIM;
+ break;
+ }
+
+ boolean logOnFrame;
+ switch(parsedLogFrequency) {
+ case CONFIG_VALUE_UNSPECIFIED:
+ Log.w(TAG, "Unspecified log frequency. Defaulting to 'log on frame'");
+ logOnFrame = true;
+ break;
+ case WindowManagerConfig.LOG_FREQUENCY_FRAME:
+ logOnFrame = true;
+ break;
+ case WindowManagerConfig.LOG_FREQUENCY_TRANSACTION:
+ logOnFrame = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized log frequency. Defaulting to 'log on frame'");
+ logOnFrame = true;
+ break;
+ }
+
+ return new Config(logLevel, logOnFrame);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
new file mode 100644
index 000000000000..653b6dac1537
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 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;
+
+import android.annotation.Nullable;
+import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket;
+import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class WindowTracingPerfetto extends WindowTracing {
+ private static final String TAG = "WindowTracing";
+
+ private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
+ private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
+ private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(
+ this::onStart, this::onStop);
+
+ WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
+ super(service, choreographer, service.mGlobalLock);
+ }
+
+ @Override
+ void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+ logAndPrintln(pw, "Log level must be configured through perfetto");
+ }
+
+ @Override
+ void setLogFrequency(boolean onFrame, PrintWriter pw) {
+ logAndPrintln(pw, "Log frequency must be configured through perfetto");
+ }
+
+ @Override
+ void setBufferCapacity(int capacity, PrintWriter pw) {
+ logAndPrintln(pw, "Buffer capacity must be configured through perfetto");
+ }
+
+ @Override
+ boolean isEnabled() {
+ return (mCountSessionsOnFrame.get() + mCountSessionsOnTransaction.get()) > 0;
+ }
+
+ @Override
+ int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ pw.println("Shell commands are ignored."
+ + " Any type of action should be performed through perfetto.");
+ return -1;
+ }
+
+ @Override
+ String getStatus() {
+ return "Status: "
+ + ((isEnabled()) ? "Enabled" : "Disabled")
+ + "\n"
+ + "Sessions logging 'on frame': " + mCountSessionsOnFrame.get()
+ + "\n"
+ + "Sessions logging 'on transaction': " + mCountSessionsOnTransaction.get()
+ + "\n";
+ }
+
+ @Override
+ protected void startTraceInternal(@Nullable PrintWriter pw) {
+ logAndPrintln(pw, "Tracing must be started through perfetto");
+ }
+
+ @Override
+ protected void stopTraceInternal(@Nullable PrintWriter pw) {
+ logAndPrintln(pw, "Tracing must be stopped through perfetto");
+ }
+
+ @Override
+ protected void saveForBugreportInternal(@Nullable PrintWriter pw) {
+ logAndPrintln(pw, "Tracing snapshot for bugreport must be handled through perfetto");
+ }
+
+ @Override
+ protected void log(String where) {
+ try {
+ boolean isStartLogEvent = where == WHERE_START_TRACING;
+ boolean isOnFrameLogEvent = where == WHERE_ON_FRAME;
+
+ mDataSource.trace((context) -> {
+ WindowTracingDataSource.Config dataSourceConfig =
+ context.getCustomTlsState().mConfig;
+
+ if (isStartLogEvent) {
+ boolean isDataSourceStarting = context.getCustomTlsState()
+ .mIsStarting.compareAndSet(true, false);
+ if (!isDataSourceStarting) {
+ return;
+ }
+ } else if (isOnFrameLogEvent != dataSourceConfig.mLogOnFrame) {
+ return;
+ }
+
+ ProtoOutputStream os = context.newTracePacket();
+ long timestamp = SystemClock.elapsedRealtimeNanos();
+ os.write(TracePacket.TIMESTAMP, timestamp);
+ final long tokenWinscopeExtensions =
+ os.start(TracePacket.WINSCOPE_EXTENSIONS);
+ final long tokenExtensionsField =
+ os.start(WinscopeExtensionsImpl.WINDOWMANAGER);
+ dumpToProto(os, dataSourceConfig.mLogLevel, where, timestamp);
+ os.end(tokenExtensionsField);
+ os.end(tokenWinscopeExtensions);
+ });
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ }
+ }
+
+ @Override
+ protected boolean shouldLogOnFrame() {
+ return mCountSessionsOnFrame.get() > 0;
+ }
+
+ @Override
+ protected boolean shouldLogOnTransaction() {
+ return mCountSessionsOnTransaction.get() > 0;
+ }
+
+ private void onStart(WindowTracingDataSource.Config config) {
+ if (config.mLogOnFrame) {
+ mCountSessionsOnFrame.incrementAndGet();
+ } else {
+ mCountSessionsOnTransaction.incrementAndGet();
+ }
+
+ Log.i(TAG, "Started with logLevel: " + config.mLogLevel
+ + " logOnFrame: " + config.mLogOnFrame);
+ log(WHERE_START_TRACING);
+ }
+
+ private void onStop(WindowTracingDataSource.Config config) {
+ if (config.mLogOnFrame) {
+ mCountSessionsOnFrame.decrementAndGet();
+ } else {
+ mCountSessionsOnTransaction.decrementAndGet();
+ }
+ Log.i(TAG, "Stopped");
+ }
+}
diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
index 4211764085b1..3559e620a350 100644
--- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
+++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
@@ -47,16 +47,13 @@ public enum DesktopModeFlagsUtil {
Flags::enableDesktopWindowingWallpaperActivity, /* shouldOverrideByDevOption= */ true);
private static final String TAG = "DesktopModeFlagsUtil";
- private static final String SYSTEM_PROPERTY_OVERRIDE_KEY =
- "sys.wmshell.desktopmode.dev_toggle_override";
-
// Function called to obtain aconfig flag value.
private final Supplier<Boolean> mFlagFunction;
// Whether the flag state should be affected by developer option.
private final boolean mShouldOverrideByDevOption;
// Local cache for toggle override, which is initialized once on its first access. It needs to
- // be refreshed only on reboots as overridden state takes effect on reboots.
+ // be refreshed only on reboots as overridden state is expected to take effect on reboots.
private static ToggleOverride sCachedToggleOverride;
DesktopModeFlagsUtil(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) {
@@ -67,9 +64,6 @@ public enum DesktopModeFlagsUtil {
/**
* Determines state of flag based on the actual flag and desktop mode developer option
* overrides.
- *
- * Note: this method makes sure that a constant developer toggle overrides is read until
- * reboot.
*/
public boolean isEnabled(Context context) {
if (!Flags.showDesktopWindowingDevOption()
@@ -102,49 +96,15 @@ public enum DesktopModeFlagsUtil {
}
/**
- * Returns {@link ToggleOverride} from a non-persistent system property if present. Otherwise
- * initializes the system property by reading Settings.Global.
+ * Returns {@link ToggleOverride} from Settings.Global set by toggle.
*/
private ToggleOverride getToggleOverrideFromSystem(Context context) {
- // A non-persistent System Property is used to store override to ensure it remains
- // constant till reboot.
- String overrideProperty = System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null);
- ToggleOverride overrideFromSystemProperties = convertToToggleOverride(overrideProperty);
-
- // If valid system property, return it
- if (overrideFromSystemProperties != null) {
- return overrideFromSystemProperties;
- }
-
- // Fallback when System Property is not present (just after reboot) or not valid (user
- // manually changed the value): Read from Settings.Global
int settingValue = Settings.Global.getInt(
context.getContentResolver(),
Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
OVERRIDE_UNSET.getSetting()
);
- ToggleOverride overrideFromSettingsGlobal =
- ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET);
- // Initialize System Property
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(settingValue));
- return overrideFromSettingsGlobal;
- }
-
- /**
- * Converts {@code intString} into {@link ToggleOverride}. Return {@code null} if
- * {@code intString} does not correspond to a {@link ToggleOverride}.
- */
- private static @Nullable ToggleOverride convertToToggleOverride(
- @Nullable String intString
- ) {
- if (intString == null) return null;
- try {
- int intValue = Integer.parseInt(intString);
- return ToggleOverride.fromSetting(intValue, null);
- } catch (NumberFormatException e) {
- Log.w(TAG, "Unknown toggleOverride int " + intString);
- return null;
- }
+ return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET);
}
/** Override state of desktop mode developer option toggle. */
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 9dc70afad9d9..4ef9cf4d2388 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -15,33 +15,168 @@
*/
#define LOG_TAG "UsbDeviceManagerJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <core_jni_helpers.h>
+#include <fcntl.h>
+#include <linux/usb/f_accessory.h>
#include <nativehelper/JNIPlatformHelp.h>
#include <nativehelper/ScopedUtfChars.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-#include "MtpDescriptors.h"
-
#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
+#include <sys/epoll.h>
#include <sys/ioctl.h>
-#include <linux/usb/f_accessory.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <thread>
+
+#include "MtpDescriptors.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+#include "jni.h"
+#include "utils/Log.h"
#define DRIVER_NAME "/dev/usb_accessory"
+#define EPOLL_MAX_EVENTS 4
+#define USB_STATE_MAX_LEN 20
namespace android
{
+static JavaVM *gvm = nullptr;
+static jmethodID gUpdateGadgetStateMethod;
+
static struct parcel_file_descriptor_offsets_t
{
jclass mClass;
jmethodID mConstructor;
} gParcelFileDescriptorOffsets;
+/*
+ * NativeGadgetMonitorThread starts a new thread to monitor udc state by epoll,
+ * convert and update the state to UsbDeviceManager.
+ */
+class NativeGadgetMonitorThread {
+ android::base::unique_fd mMonitorFd;
+ int mPipefd[2];
+ std::thread mThread;
+ jobject mCallbackObj;
+ std::string mGadgetState;
+
+ void handleStateUpdate(const char *state) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ std::string gadgetState;
+
+ if (!std::strcmp(state, "not attached\n")) {
+ gadgetState = "DISCONNECTED";
+ } else if (!std::strcmp(state, "attached\n") || !std::strcmp(state, "powered\n") ||
+ !std::strcmp(state, "default\n") || !std::strcmp(state, "addressed\n")) {
+ gadgetState = "CONNECTED";
+ } else if (!std::strcmp(state, "configured\n")) {
+ gadgetState = "CONFIGURED";
+ } else if (!std::strcmp(state, "suspended\n")) {
+ return;
+ } else {
+ ALOGE("Unknown gadget state %s", state);
+ return;
+ }
+
+ if (mGadgetState.compare(gadgetState)) {
+ mGadgetState = gadgetState;
+ jstring obj = env->NewStringUTF(gadgetState.c_str());
+ env->CallVoidMethod(mCallbackObj, gUpdateGadgetStateMethod, obj);
+ }
+ }
+
+ int setupEpoll(android::base::unique_fd &epollFd) {
+ struct epoll_event ev;
+
+ ev.data.fd = mMonitorFd.get();
+ ev.events = EPOLLPRI;
+ if (epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, mMonitorFd.get(), &ev) != 0) {
+ ALOGE("epoll_ctl failed for monitor fd; errno=%d", errno);
+ return errno;
+ }
+
+ ev.data.fd = mPipefd[0];
+ ev.events = EPOLLIN;
+ if (epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, mPipefd[0], &ev) != 0) {
+ ALOGE("epoll_ctl failed for pipe fd; errno=%d", errno);
+ return errno;
+ }
+
+ return 0;
+ }
+
+ void monitorLoop() {
+ android::base::unique_fd epollFd(epoll_create(EPOLL_MAX_EVENTS));
+ if (epollFd.get() == -1) {
+ ALOGE("epoll_create failed; errno=%d", errno);
+ return;
+ }
+ if (setupEpoll(epollFd) != 0) return;
+
+ JNIEnv *env = nullptr;
+ JavaVMAttachArgs aargs = {JNI_VERSION_1_4, "NativeGadgetMonitorThread", nullptr};
+ if (gvm->AttachCurrentThread(&env, &aargs) != JNI_OK || env == nullptr) {
+ ALOGE("Couldn't attach thread");
+ return;
+ }
+
+ struct epoll_event events[EPOLL_MAX_EVENTS];
+ int nevents = 0;
+ while (true) {
+ nevents = epoll_wait(epollFd.get(), events, EPOLL_MAX_EVENTS, -1);
+ if (nevents < 0) {
+ ALOGE("usb epoll_wait failed; errno=%d", errno);
+ continue;
+ }
+ for (int i = 0; i < nevents; ++i) {
+ int fd = events[i].data.fd;
+ if (fd == mPipefd[0]) {
+ goto exit;
+ } else if (fd == mMonitorFd.get()) {
+ char state[USB_STATE_MAX_LEN] = {0};
+ lseek(fd, 0, SEEK_SET);
+ read(fd, &state, USB_STATE_MAX_LEN);
+ handleStateUpdate(state);
+ }
+ }
+ }
+
+ exit:
+ auto res = gvm->DetachCurrentThread();
+ ALOGE_IF(res != JNI_OK, "Couldn't detach thread");
+ return;
+ }
+
+ void stop() {
+ if (mThread.joinable()) {
+ int c = 'q';
+ write(mPipefd[1], &c, 1);
+ mThread.join();
+ }
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NativeGadgetMonitorThread);
+
+public:
+ explicit NativeGadgetMonitorThread(jobject obj, android::base::unique_fd monitorFd)
+ : mMonitorFd(std::move(monitorFd)), mGadgetState("") {
+ mCallbackObj = AndroidRuntime::getJNIEnv()->NewGlobalRef(obj);
+ pipe(mPipefd);
+ mThread = std::thread(&NativeGadgetMonitorThread::monitorLoop, this);
+ }
+
+ ~NativeGadgetMonitorThread() {
+ stop();
+ close(mPipefd[0]);
+ close(mPipefd[1]);
+ AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallbackObj);
+ }
+};
+static std::unique_ptr<NativeGadgetMonitorThread> sGadgetMonitorThread;
+
static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strArray, int index)
{
char buffer[256];
@@ -135,6 +270,41 @@ static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject
return jifd;
}
+static jboolean android_server_UsbDeviceManager_startGadgetMonitor(JNIEnv *env, jobject thiz,
+ jstring jUdcName) {
+ std::string filePath;
+ ScopedUtfChars udcName(env, jUdcName);
+
+ filePath = "/sys/class/udc/" + std::string(udcName.c_str()) + "/state";
+ android::base::unique_fd fd(open(filePath.c_str(), O_RDONLY));
+
+ if (fd.get() == -1) {
+ ALOGE("Cannot open %s", filePath.c_str());
+ return JNI_FALSE;
+ }
+
+ ALOGI("Start monitoring %s", filePath.c_str());
+ sGadgetMonitorThread.reset(new NativeGadgetMonitorThread(thiz, std::move(fd)));
+
+ return JNI_TRUE;
+}
+
+static void android_server_UsbDeviceManager_stopGadgetMonitor(JNIEnv *env, jobject /* thiz */) {
+ sGadgetMonitorThread.reset();
+ return;
+}
+
+static jstring android_server_UsbDeviceManager_waitAndGetProperty(JNIEnv *env, jobject thiz,
+ jstring jPropName) {
+ ScopedUtfChars propName(env, jPropName);
+ std::string propValue;
+
+ while (!android::base::WaitForPropertyCreation(propName.c_str()));
+ propValue = android::base::GetProperty(propName.c_str(), "" /* default */);
+
+ return env->NewStringUTF(propValue.c_str());
+}
+
static const JNINativeMethod method_table[] = {
{"nativeGetAccessoryStrings", "()[Ljava/lang/String;",
(void *)android_server_UsbDeviceManager_getAccessoryStrings},
@@ -143,16 +313,26 @@ static const JNINativeMethod method_table[] = {
{"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested},
{"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
(void *)android_server_UsbDeviceManager_openControl},
+ {"nativeStartGadgetMonitor", "(Ljava/lang/String;)Z",
+ (void *)android_server_UsbDeviceManager_startGadgetMonitor},
+ {"nativeStopGadgetMonitor", "()V",
+ (void *)android_server_UsbDeviceManager_stopGadgetMonitor},
+ {"nativeWaitAndGetProperty", "(Ljava/lang/String;)Ljava/lang/String;",
+ (void *)android_server_UsbDeviceManager_waitAndGetProperty},
};
-int register_android_server_UsbDeviceManager(JNIEnv *env)
-{
+int register_android_server_UsbDeviceManager(JavaVM *vm, JNIEnv *env) {
+ gvm = vm;
+
jclass clazz = env->FindClass("com/android/server/usb/UsbDeviceManager");
if (clazz == NULL) {
ALOGE("Can't find com/android/server/usb/UsbDeviceManager");
return -1;
}
+ gUpdateGadgetStateMethod =
+ GetMethodIDOrDie(env, clazz, "updateGadgetState", "(Ljava/lang/String;)V");
+
clazz = env->FindClass("android/os/ParcelFileDescriptor");
LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
@@ -163,5 +343,4 @@ int register_android_server_UsbDeviceManager(JNIEnv *env)
return jniRegisterNativeMethods(env, "com/android/server/usb/UsbDeviceManager",
method_table, NELEM(method_table));
}
-
};
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5719810abc5a..4d6a90c81b27 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -104,7 +104,6 @@ static const char* VELOCITYTRACKER_STRATEGY = "velocitytracker_strategy";
static struct {
jclass clazz;
- jmethodID notifyConfigurationChanged;
jmethodID notifyInputDevicesChanged;
jmethodID notifySwitch;
jmethodID notifyInputChannelBroken;
@@ -314,7 +313,6 @@ public:
void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
- void notifyConfigurationChanged(nsecs_t when) override;
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -940,18 +938,6 @@ void NativeInputManager::notifySwitch(nsecs_t when,
checkAndClearExceptionFromCallback(env, "notifySwitch");
}
-void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
- ALOGD("notifyConfigurationChanged - when=%lld", when);
-#endif
- ATRACE_CALL();
-
- JNIEnv* env = jniEnv();
-
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyConfigurationChanged, when);
- checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
-}
-
static jobject getInputApplicationHandleObjLocalRef(
JNIEnv* env, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
if (inputApplicationHandle == nullptr) {
@@ -2873,9 +2859,6 @@ int register_android_server_InputManager(JNIEnv* env) {
FIND_CLASS(clazz, "com/android/server/input/InputManagerService");
gServiceClassInfo.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
- GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz,
- "notifyConfigurationChanged", "(J)V");
-
GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
"notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 6464081d615a..314ff9d3c808 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -37,7 +37,7 @@ int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
-int register_android_server_UsbDeviceManager(JNIEnv* env);
+int register_android_server_UsbDeviceManager(JavaVM* vm, JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
@@ -96,7 +96,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_SerialService(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
- register_android_server_UsbDeviceManager(env);
+ register_android_server_UsbDeviceManager(vm, env);
register_android_server_UsbAlsaJackDetector(env);
register_android_server_UsbAlsaMidiDevice(env);
register_android_server_UsbHostManager(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index dc8cec91001b..6a0dd5a04f82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -182,6 +182,7 @@ class ActiveAdmin {
private static final String TAG_CREDENTIAL_MANAGER_POLICY = "credential-manager-policy";
private static final String TAG_DIALER_PACKAGE = "dialer_package";
private static final String TAG_SMS_PACKAGE = "sms_package";
+ private static final String TAG_PROVISIONING_CONTEXT = "provisioning-context";
// If the ActiveAdmin is a permission-based admin, then info will be null because the
// permission-based admin is not mapped to a device administrator component.
@@ -359,6 +360,8 @@ class ActiveAdmin {
int mWifiMinimumSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN;
String mDialerPackage;
String mSmsPackage;
+ private String mProvisioningContext;
+ private static final int PROVISIONING_CONTEXT_LENGTH_LIMIT = 1000;
ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
this.userId = -1;
@@ -404,6 +407,23 @@ class ActiveAdmin {
return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
}
+ /**
+ * Stores metadata about context of setting an active admin
+ * @param provisioningContext some metadata, for example test method name
+ */
+ public void setProvisioningContext(@Nullable String provisioningContext) {
+ if (Flags.provisioningContextParameter()
+ && !TextUtils.isEmpty(provisioningContext)
+ && !provisioningContext.isBlank()) {
+ if (provisioningContext.length() > PROVISIONING_CONTEXT_LENGTH_LIMIT) {
+ mProvisioningContext = provisioningContext.substring(
+ 0, PROVISIONING_CONTEXT_LENGTH_LIMIT);
+ } else {
+ mProvisioningContext = provisioningContext;
+ }
+ }
+ }
+
void writeToXml(TypedXmlSerializer out)
throws IllegalArgumentException, IllegalStateException, IOException {
if (info != null) {
@@ -694,6 +714,12 @@ class ActiveAdmin {
if (!TextUtils.isEmpty(mSmsPackage)) {
writeAttributeValueToXml(out, TAG_SMS_PACKAGE, mSmsPackage);
}
+
+ if (Flags.provisioningContextParameter() && !TextUtils.isEmpty(mProvisioningContext)) {
+ out.startTag(null, TAG_PROVISIONING_CONTEXT);
+ out.attribute(null, ATTR_VALUE, mProvisioningContext);
+ out.endTag(null, TAG_PROVISIONING_CONTEXT);
+ }
}
private void writePackagePolicy(TypedXmlSerializer out, String tag,
@@ -1006,6 +1032,9 @@ class ActiveAdmin {
mDialerPackage = parser.getAttributeValue(null, ATTR_VALUE);
} else if (TAG_SMS_PACKAGE.equals(tag)) {
mSmsPackage = parser.getAttributeValue(null, ATTR_VALUE);
+ } else if (Flags.provisioningContextParameter()
+ && TAG_PROVISIONING_CONTEXT.equals(tag)) {
+ mProvisioningContext = parser.getAttributeValue(null, ATTR_VALUE);
} else {
Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
@@ -1496,5 +1525,10 @@ class ActiveAdmin {
pw.println(mDialerPackage);
pw.print("mSmsPackage=");
pw.println(mSmsPackage);
+
+ if (Flags.provisioningContextParameter()) {
+ pw.print("mProvisioningContext=");
+ pw.println(mProvisioningContext);
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 032d6b56af1b..7cb8ace697ec 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1378,7 +1378,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Clear always-on configuration if it wasn't set by the admin.
if (adminConfiguredVpnPkg == null) {
- mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(userId, null, false, null);
+ VpnManager vpnManager = mInjector.getVpnManager();
+ if (vpnManager != null) {
+ vpnManager.setAlwaysOnVpnPackageForUser(userId, null, false, null);
+ }
}
// Clear app authorizations to establish VPNs. When DISALLOW_CONFIG_VPN is enforced apps
@@ -1789,6 +1792,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mContext.getSystemService(ConnectivityManager.class);
}
+ @Nullable
VpnManager getVpnManager() {
return mContext.getSystemService(VpnManager.class);
}
@@ -3943,10 +3947,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* @param adminReceiver The admin to add
* @param refreshing true = update an active admin, no error
+ * @param userHandle which user this admin will be set on
+ * @param provisioningContext additional information for debugging
*/
@Override
public void setActiveAdmin(
- ComponentName adminReceiver, boolean refreshing, int userHandle) {
+ ComponentName adminReceiver,
+ boolean refreshing,
+ int userHandle,
+ @Nullable String provisioningContext
+ ) {
if (!mHasFeature) {
return;
}
@@ -3972,6 +3982,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
newAdmin.testOnlyAdmin =
(existingAdmin != null) ? existingAdmin.testOnlyAdmin
: isPackageTestOnly(adminReceiver.getPackageName(), userHandle);
+ newAdmin.setProvisioningContext(provisioningContext);
policy.mAdminMap.put(adminReceiver, newAdmin);
int replaceIndex = -1;
final int N = policy.mAdminList.size();
@@ -7697,8 +7708,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
// If some package is uninstalled after the check above, it will be ignored by CM.
- if (!mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(
- userId, vpnPackage, lockdown, lockdownAllowlist)) {
+ VpnManager vpnManager = mInjector.getVpnManager();
+ if (vpnManager == null
+ || !mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(
+ userId, vpnPackage, lockdown, lockdownAllowlist)) {
throw new UnsupportedOperationException();
}
});
@@ -7746,8 +7759,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ VpnManager vpnManager = mInjector.getVpnManager();
+ if (vpnManager == null) {
+ return null;
+ }
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
+ () -> vpnManager.getAlwaysOnVpnPackageForUser(caller.getUserId()));
}
@Override
@@ -7774,8 +7791,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
+ VpnManager vpnManager = mInjector.getVpnManager();
+ if (vpnManager == null) {
+ return false;
+ }
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getVpnManager().isVpnLockdownEnabled(caller.getUserId()));
+ () -> vpnManager.isVpnLockdownEnabled(caller.getUserId()));
}
@Override
@@ -7797,8 +7818,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ VpnManager vpnManager = mInjector.getVpnManager();
+ if (vpnManager == null) {
+ return null;
+ }
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
+ () -> vpnManager.getVpnLockdownAllowlist(caller.getUserId()));
}
private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc,
@@ -12830,7 +12855,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
});
// Set admin.
- setActiveAdmin(profileOwner, /* refreshing= */ true, userId);
+ setActiveAdmin(profileOwner, /* refreshing= */ true, userId, null);
setProfileOwner(profileOwner, userId);
synchronized (getLockObject()) {
@@ -21883,7 +21908,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@UserIdInt int userId, @UserIdInt int callingUserId, ComponentName adminComponent) {
final String adminPackage = adminComponent.getPackageName();
enablePackage(adminPackage, callingUserId);
- setActiveAdmin(adminComponent, /* refreshing= */ true, userId);
+ setActiveAdmin(adminComponent, /* refreshing= */ true, userId, null);
}
private void enablePackage(String packageName, @UserIdInt int userId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index eb893fcfee1f..0cd5b47b75c0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -17,6 +17,7 @@ package com.android.server.devicepolicy;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.ShellCommand;
import android.os.SystemClock;
@@ -46,11 +47,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
private static final String USER_OPTION = "--user";
private static final String DO_ONLY_OPTION = "--device-owner-only";
+ private static final String PROVISIONING_CONTEXT_OPTION = "--provisioning-context";
private final DevicePolicyManagerService mService;
private int mUserId = UserHandle.USER_SYSTEM;
private ComponentName mComponent;
private boolean mSetDoOnly;
+ private String mProvisioningContext = null;
DevicePolicyManagerServiceShellCommand(DevicePolicyManagerService service) {
mService = Objects.requireNonNull(service);
@@ -127,15 +130,28 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
pw.printf(" Lists the device / profile owners per user \n\n");
pw.printf(" %s\n", CMD_LIST_POLICY_EXEMPT_APPS);
pw.printf(" Lists the apps that are exempt from policies\n\n");
- pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
- CMD_SET_ACTIVE_ADMIN, USER_OPTION);
- pw.printf(" Sets the given component as active admin for an existing user.\n\n");
- pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
- + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION);
- pw.printf(" Sets the given component as active admin, and its package as device owner."
- + "\n\n");
- pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
- CMD_SET_PROFILE_OWNER, USER_OPTION);
+ if (Flags.provisioningContextParameter()) {
+ pw.printf(" %s [ %s <USER_ID> | current ] [ %s <PROVISIONING_CONTEXT>] <COMPONENT>\n",
+ CMD_SET_ACTIVE_ADMIN, USER_OPTION, PROVISIONING_CONTEXT_OPTION);
+ pw.printf(" Sets the given component as active admin for an existing user.\n\n");
+ pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
+ + " [ %s <PROVISIONING_CONTEXT>] <COMPONENT>\n",
+ CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION, PROVISIONING_CONTEXT_OPTION);
+ pw.printf(" Sets the given component as active admin, and its package as device"
+ + " owner.\n\n");
+ pw.printf(" %s [ %s <USER_ID> | current ] [ %s <PROVISIONING_CONTEXT>] <COMPONENT>\n",
+ CMD_SET_PROFILE_OWNER, USER_OPTION, PROVISIONING_CONTEXT_OPTION);
+ } else {
+ pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+ CMD_SET_ACTIVE_ADMIN, USER_OPTION);
+ pw.printf(" Sets the given component as active admin for an existing user.\n\n");
+ pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
+ + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION);
+ pw.printf(" Sets the given component as active admin, and its package as device"
+ + " owner.\n\n");
+ pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+ CMD_SET_PROFILE_OWNER, USER_OPTION);
+ }
pw.printf(" Sets the given component as active admin and profile owner for an existing "
+ "user.\n\n");
pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
@@ -243,7 +259,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
private int runSetActiveAdmin(PrintWriter pw) {
parseArgs();
- mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
+ mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId, mProvisioningContext);
pw.printf("Success: Active admin set to component %s\n", mComponent.flattenToShortString());
return 0;
@@ -253,7 +269,12 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
parseArgs();
boolean isAdminAdded = false;
try {
- mService.setActiveAdmin(mComponent, /* refreshing= */ false, mUserId);
+ mService.setActiveAdmin(
+ mComponent,
+ /* refreshing= */ false,
+ mUserId,
+ mProvisioningContext
+ );
isAdminAdded = true;
} catch (IllegalArgumentException e) {
pw.printf("%s was already an admin for user %d. No need to set it again.\n",
@@ -291,7 +312,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
private int runSetProfileOwner(PrintWriter pw) {
parseArgs();
- mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
+ mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId, mProvisioningContext);
try {
if (!mService.setProfileOwner(mComponent, mUserId)) {
@@ -363,6 +384,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
} else if (DO_ONLY_OPTION.equals(opt)) {
mSetDoOnly = true;
+ } else if (PROVISIONING_CONTEXT_OPTION.equals(opt)) {
+ mProvisioningContext = getNextArgRequired();
} else {
throw new IllegalArgumentException("Unknown option: " + opt);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index cbd28475bc26..6e038f9b67a0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.VerifierDeviceIdentity;
import android.net.wifi.WifiManager;
import android.os.Build;
@@ -77,13 +78,14 @@ class EnterpriseSpecificIdCalculator {
mMeid = meid;
mSerialNumber = Build.getSerial();
WifiManager wifiManager = context.getSystemService(WifiManager.class);
- Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
- final String[] macAddresses = wifiManager.getFactoryMacAddresses();
- if (macAddresses == null || macAddresses.length == 0) {
- mMacAddress = "";
- } else {
- mMacAddress = macAddresses[0];
+ String macAddress = "";
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ final String[] macAddresses = wifiManager.getFactoryMacAddresses();
+ if (macAddresses != null && macAddresses.length > 0) {
+ macAddress = macAddresses[0];
+ }
}
+ mMacAddress = macAddress;
}
private static String getPaddedTruncatedString(String input, int maxLength) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index db4b171152a7..9e8811f419a2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -153,6 +153,7 @@ import com.android.server.contentsuggestions.ContentSuggestionsManagerService;
import com.android.server.contextualsearch.ContextualSearchManagerService;
import com.android.server.coverage.CoverageService;
import com.android.server.cpu.CpuMonitorService;
+import com.android.server.crashrecovery.CrashRecoveryModule;
import com.android.server.credentials.CredentialManagerService;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -381,8 +382,6 @@ public final class SystemServer implements Dumpable {
+ "OnDevicePersonalizationSystemService$Lifecycle";
private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
"com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
- private static final String CRASHRECOVERY_MODULE_LIFECYCLE_CLASS =
- "com.android.server.crashrecovery.CrashRecoveryModule$Lifecycle";
/*
@@ -2939,7 +2938,7 @@ public final class SystemServer implements Dumpable {
if (Flags.refactorCrashrecovery()) {
t.traceBegin("StartCrashRecoveryModule");
- mSystemServiceManager.startService(CRASHRECOVERY_MODULE_LIFECYCLE_CLASS);
+ mSystemServiceManager.startService(CrashRecoveryModule.Lifecycle.class);
t.traceEnd();
} else {
if (Flags.recoverabilityDetection()) {
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 3ed6ad78343b..acdbbdee7d67 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -398,13 +398,13 @@ public final class ProfcollectForwardingService extends SystemService {
if (randomNum >= traceFrequency) {
return;
}
- // For a small percentage a traces, we collect the initialization behavior.
- boolean traceInitialization = ThreadLocalRandom.current().nextInt(10) < 1;
- int traceDelay = traceInitialization ? 0 : 1000;
- String traceTag = traceInitialization ? "camera_init" : "camera";
+ final int traceDelay = 1000;
+ final int traceDuration = 5000;
+ final String traceTag = "camera";
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider");
+ mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
+ traceDuration);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
index 63224bb2aa3f..c54ff5fb2f69 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
@@ -25,7 +25,7 @@ import android.util.AtomicFile;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
@@ -54,7 +54,8 @@ public final class AdditionalSubtypeUtilsTest {
// Save & load.
AtomicFile atomicFile = new AtomicFile(
- new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
+ new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+ "subtypes.xml"));
AdditionalSubtypeUtils.saveToFile(AdditionalSubtypeMap.of(allSubtypes),
InputMethodMap.of(methodMap), atomicFile);
AdditionalSubtypeMap loadedSubtypes = AdditionalSubtypeUtils.loadFromFile(atomicFile);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 267ce26515d7..ec9bfa7200c6 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -59,6 +59,7 @@ import android.window.ImeOnBackInvokedDispatcher;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IInputMethodSession;
@@ -269,8 +270,15 @@ public class InputMethodManagerServiceTestBase {
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
lifecycle.onStart();
- // Emulate that the user initialization is done.
+ // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
+ // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId);
+ final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
+ mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(mCallingUserId, settings);
+
+ // Emulate that the user initialization is done.
mInputMethodManagerService.getUserData(mCallingUserId).mBackgroundLoadLatch.countDown();
// After this boot phase, services can broadcast Intents.
@@ -283,6 +291,8 @@ public class InputMethodManagerServiceTestBase {
@After
public void tearDown() {
+ InputMethodSettingsRepository.remove(mCallingUserId);
+
if (mInputMethodManagerService != null) {
mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 2857619c70d3..3cf895ee7204 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -40,7 +40,7 @@ import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.inputmethod.StartInputFlags;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
index 05c243fda2b8..36baacc529d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -301,7 +303,7 @@ public class BrightnessThrottlerTest {
new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f);
List<ThrottlingLevel> levels = new ArrayList<>(List.of(level));
final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
- final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+ final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
final BrightnessThrottler throttler =
createThrottlerSupportedWithTempSensor(data, tempSensor);
assertTrue(throttler.deviceSupportsThrottling());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d2686372f550..2b03dc4f78ad 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -31,6 +31,7 @@ import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -2423,7 +2424,7 @@ public class DisplayManagerServiceTest {
String testSensorType = "testType";
Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
- SensorData sensorData = new SensorData(testSensorType, testSensorName,
+ SensorData sensorData = createSensorData(testSensorType, testSensorName,
/* minRefreshRate= */ 10f, /* maxRefreshRate= */ 100f);
when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index c6aea5a290e9..8ed38a6d0cad 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -85,7 +86,6 @@ import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.config.HysteresisLevels;
-import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.flags.Flags;
import com.android.server.display.layout.Layout;
@@ -2159,13 +2159,13 @@ public final class DisplayPowerControllerTest {
when(displayDeviceMock.getNameLocked()).thenReturn(displayName);
when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
- new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
+ createSensorData(Sensor.STRING_TYPE_PROXIMITY));
when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
- new SensorData());
+ createSensorData());
when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
- new SensorData(Sensor.STRING_TYPE_LIGHT, null));
+ createSensorData(Sensor.STRING_TYPE_LIGHT));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
when(displayDeviceConfigMock.getDefaultDozeBrightness())
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index ebd6614aba14..29f07227a12d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -37,7 +38,6 @@ import android.view.Display;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.server.display.config.SensorData;
import com.android.server.testutils.OffsettableClock;
import org.junit.Before;
@@ -75,7 +75,7 @@ public final class DisplayPowerProximityStateControllerTest {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
+ createSensorData(Sensor.STRING_TYPE_PROXIMITY));
setUpProxSensor();
DisplayPowerProximityStateController.Injector injector =
new DisplayPowerProximityStateController.Injector() {
@@ -165,7 +165,7 @@ public final class DisplayPowerProximityStateControllerTest {
@Test
public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
- when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData());
mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
@@ -176,7 +176,7 @@ public final class DisplayPowerProximityStateControllerTest {
@Test
public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
throws Exception {
- when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData());
when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -189,7 +189,7 @@ public final class DisplayPowerProximityStateControllerTest {
@Test
public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
throws Exception {
- when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData());
when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -216,7 +216,7 @@ public final class DisplayPowerProximityStateControllerTest {
public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
+ createSensorData(Sensor.STRING_TYPE_PROXIMITY));
Sensor newProxSensor = TestUtils.createSensor(
Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index e982153acbd1..0ce9233b4b6f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,8 +42,8 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.display.DisplayBrightnessState;
-import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -89,6 +90,10 @@ public class BrightnessClamperControllerTest {
@Mock
private BrightnessModifier mMockModifier;
@Mock
+ private TestStatefulModifier mMockStatefulModifier;
+ @Mock
+ private TestDisplayListenerModifier mMockDisplayListenerModifier;
+ @Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
@Mock
@@ -99,7 +104,8 @@ public class BrightnessClamperControllerTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier));
+ mTestInjector = new TestInjector(List.of(mMockClamper),
+ List.of(mMockModifier, mMockStatefulModifier, mMockDisplayListenerModifier));
when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID);
when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
@@ -168,6 +174,13 @@ public class BrightnessClamperControllerTest {
}
@Test
+ public void testOnDisplayChanged_DelegatesToDisplayListeners() {
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockDisplayListenerModifier).onDisplayChanged(mMockDisplayDeviceData);
+ }
+
+ @Test
public void testOnDisplayChanged_doesNotRestartLightSensor() {
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
@@ -189,6 +202,8 @@ public class BrightnessClamperControllerTest {
mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
verify(mMockModifier).apply(eq(mMockRequest), any());
+ verify(mMockDisplayListenerModifier).apply(eq(mMockRequest), any());
+ verify(mMockStatefulModifier).apply(eq(mMockRequest), any());
}
@Test
@@ -326,11 +341,41 @@ public class BrightnessClamperControllerTest {
verify(mMockClamper).stop();
}
+ @Test
+ public void test_doesNotNotifyExternalListener_aggregatedStateNotChanged() {
+ mTestInjector.mCapturedChangeListener.onChanged();
+ mTestHandler.flush();
+
+ verify(mMockExternalListener, never()).onChanged();
+ }
+
+ @Test
+ public void test_notifiesExternalListener_aggregatedStateChanged() {
+ doAnswer((invocation) -> {
+ ModifiersAggregatedState argument = invocation.getArgument(0);
+ // we need to do changes in AggregatedState to trigger onChange
+ argument.mMaxHdrBrightness = 0.5f;
+ return null;
+ }).when(mMockStatefulModifier).applyStateChange(any());
+ mTestInjector.mCapturedChangeListener.onChanged();
+ mTestHandler.flush();
+
+ verify(mMockExternalListener).onChanged();
+ }
+
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager);
}
+ interface TestDisplayListenerModifier extends BrightnessStateModifier,
+ BrightnessClamperController.DisplayDeviceDataListener {
+ }
+
+ interface TestStatefulModifier extends BrightnessStateModifier,
+ BrightnessClamperController.StatefulModifier {
+ }
+
private class TestInjector extends BrightnessClamperController.Injector {
private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
@@ -366,7 +411,7 @@ public class BrightnessClamperControllerTest {
@Override
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, BrightnessClamperController.ClamperChangeListener listener,
- DisplayDeviceConfig displayDeviceConfig) {
+ BrightnessClamperController.DisplayDeviceData displayDeviceData) {
return mModifiers;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
index 34f352e7bf54..9d16594fae93 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -16,6 +16,8 @@
package com.android.server.display.brightness.clamper;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -189,7 +191,7 @@ public class BrightnessThermalClamperTest {
final int severity = PowerManager.THERMAL_STATUS_SEVERE;
IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
// Update config to listen to display type sensor.
- final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+ final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
final TestThermalData thermalData =
new TestThermalData(
DISPLAY_ID,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
new file mode 100644
index 000000000000..5fd848f6adcc
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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.display.brightness.clamper
+
+import android.os.IBinder
+import android.view.Display
+import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.brightness.clamper.BrightnessClamperController.DisplayDeviceData
+
+fun createDisplayDeviceData(
+ displayDeviceConfig: DisplayDeviceConfig,
+ displayToken: IBinder,
+ uniqueDisplayId: String = "displayId",
+ thermalThrottlingDataId: String = "thermalId",
+ powerThrottlingDataId: String = "powerId",
+ width: Int = 100,
+ height: Int = 100,
+ displayId: Int = Display.DEFAULT_DISPLAY
+): DisplayDeviceData {
+ return DisplayDeviceData(
+ uniqueDisplayId,
+ thermalThrottlingDataId,
+ powerThrottlingDataId,
+ displayDeviceConfig,
+ width,
+ height,
+ displayToken,
+ displayId
+ )
+} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
new file mode 100644
index 000000000000..0ed96ae52491
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2024 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.display.brightness.clamper
+
+import android.hardware.display.DisplayManagerInternal
+import android.os.IBinder
+import android.os.PowerManager.BRIGHTNESS_MAX
+import android.util.Spline
+import android.view.SurfaceControlHdrLayerInfoListener
+import androidx.test.filters.SmallTest
+import com.android.server.display.DisplayBrightnessState
+import com.android.server.display.DisplayBrightnessState.BRIGHTNESS_NOT_SET
+import com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET
+import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState
+import com.android.server.display.brightness.clamper.HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO
+import com.android.server.display.brightness.clamper.HdrBrightnessModifier.Injector
+import com.android.server.display.config.HdrBrightnessData
+import com.android.server.display.config.createHdrBrightnessData
+import com.android.server.testutils.OffsettableClock
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val SEND_TIME_TOLERANCE: Long = 100
+
+@SmallTest
+class HdrBrightnessModifierTest {
+
+ private val stoppedClock = OffsettableClock.Stopped()
+ private val testHandler = TestHandler(null, stoppedClock)
+ private val testInjector = TestInjector()
+ private val mockChangeListener = mock<ClamperChangeListener>()
+ private val mockDisplayDeviceConfig = mock<DisplayDeviceConfig>()
+ private val mockDisplayBinder = mock<IBinder>()
+ private val mockDisplayBinderOther = mock<IBinder>()
+ private val mockSpline = mock<Spline>()
+ private val mockRequest = mock<DisplayManagerInternal.DisplayPowerRequest>()
+
+ private lateinit var modifier: HdrBrightnessModifier
+ private val dummyData = createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinder)
+
+ @Test
+ fun `change listener is not called on init`() {
+ initHdrModifier()
+
+ verify(mockChangeListener, never()).onChanged()
+ }
+
+ @Test
+ fun `hdr listener registered on init if hdr data is present`() {
+ initHdrModifier()
+
+ assertThat(testInjector.registeredHdrListener).isNotNull()
+ assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinder)
+ }
+
+ @Test
+ fun `hdr listener not registered on init if hdr data is missing`() {
+ initHdrModifier(null)
+
+ testHandler.flush()
+
+ assertThat(testInjector.registeredHdrListener).isNull()
+ assertThat(testInjector.registeredToken).isNull()
+ }
+
+ @Test
+ fun `unsubscribes hdr listener when display changed with no hdr data`() {
+ initHdrModifier()
+
+ whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null)
+ modifier.onDisplayChanged(dummyData)
+ testHandler.flush()
+
+ assertThat(testInjector.registeredHdrListener).isNull()
+ assertThat(testInjector.registeredToken).isNull()
+ verify(mockChangeListener, never()).onChanged()
+ }
+
+ @Test
+ fun `resubscribes hdr listener when display changed with different token`() {
+ initHdrModifier()
+
+ modifier.onDisplayChanged(
+ createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinderOther))
+ testHandler.flush()
+
+ assertThat(testInjector.registeredHdrListener).isNotNull()
+ assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinderOther)
+ verify(mockChangeListener, never()).onChanged()
+ }
+
+ @Test
+ fun `test NO_HDR mode`() {
+ initHdrModifier()
+ // screen size = 10_000
+ setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+ minimumHdrPercentOfScreenForNbm = 0.5f,
+ minimumHdrPercentOfScreenForHbm = 0.7f,
+ sdrToHdrRatioSpline = mockSpline
+ ))
+
+ // hdr size = 900
+ val desiredMaxHdrRatio = 8f
+ setupHdrLayer(width = 30, height = 30, maxHdrRatio = desiredMaxHdrRatio)
+
+ assertModifierState()
+ }
+
+ @Test
+ fun `test NBM_HDR mode`() {
+ initHdrModifier()
+ // screen size = 10_000
+ val transitionPoint = 0.55f
+ setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+ minimumHdrPercentOfScreenForNbm = 0.5f,
+ minimumHdrPercentOfScreenForHbm = 0.7f,
+ transitionPoint = transitionPoint,
+ sdrToHdrRatioSpline = mockSpline
+ ))
+ // hdr size = 5_100
+ val desiredMaxHdrRatio = 8f
+ setupHdrLayer(width = 100, height = 51, maxHdrRatio = desiredMaxHdrRatio)
+
+ whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+ 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(0.85f)
+
+ assertModifierState(
+ maxBrightness = transitionPoint,
+ hdrRatio = desiredMaxHdrRatio,
+ hdrBrightness = transitionPoint,
+ spline = mockSpline
+ )
+ }
+
+ @Test
+ fun `test HBM_HDR mode`() {
+ initHdrModifier()
+ // screen size = 10_000
+ setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+ minimumHdrPercentOfScreenForNbm = 0.5f,
+ minimumHdrPercentOfScreenForHbm = 0.7f,
+ transitionPoint = 0.55f,
+ sdrToHdrRatioSpline = mockSpline
+ ))
+ // hdr size = 7_100
+ val desiredMaxHdrRatio = 8f
+ setupHdrLayer(width = 100, height = 71, maxHdrRatio = desiredMaxHdrRatio)
+
+ val expectedHdrBrightness = 0.92f
+ whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+ 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness)
+
+ assertModifierState(
+ hdrRatio = desiredMaxHdrRatio,
+ hdrBrightness = expectedHdrBrightness,
+ spline = mockSpline
+ )
+ }
+
+ @Test
+ fun `test display change no HDR content`() {
+ initHdrModifier()
+ setupDisplay(width = 100, height = 100)
+ assertModifierState()
+ clearInvocations(mockChangeListener)
+ // display change, new instance of HdrBrightnessData
+ setupDisplay(width = 100, height = 100)
+
+ assertModifierState()
+ verify(mockChangeListener, never()).onChanged()
+ }
+
+ @Test
+ fun `test display change with HDR content`() {
+ initHdrModifier()
+ setupDisplay(width = 100, height = 100)
+ setupHdrLayer(width = 100, height = 100, maxHdrRatio = 5f)
+ assertModifierState(
+ hdrBrightness = 0f,
+ hdrRatio = 5f,
+ spline = mockSpline
+ )
+ clearInvocations(mockChangeListener)
+ // display change, new instance of HdrBrightnessData
+ setupDisplay(width = 100, height = 100)
+
+ assertModifierState(
+ hdrBrightness = 0f,
+ hdrRatio = 5f,
+ spline = mockSpline
+ )
+ // new instance of HdrBrightnessData received, notify listener
+ verify(mockChangeListener).onChanged()
+ }
+
+ @Test
+ fun `test ambient lux decrease above maxBrightnessLimits no HDR`() {
+ initHdrModifier()
+ modifier.setAmbientLux(1000f)
+ setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+ maxBrightnessLimits = mapOf(Pair(500f, 0.6f))
+ ))
+
+ modifier.setAmbientLux(500f)
+ // verify debounce is not scheduled
+ assertThat(testHandler.hasMessagesOrCallbacks()).isFalse()
+
+ assertModifierState()
+ verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any())
+ }
+
+ @Test
+ fun `test ambient lux decrease above maxBrightnessLimits with HDR`() {
+ initHdrModifier()
+ modifier.setAmbientLux(1000f)
+ setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData(
+ maxBrightnessLimits = mapOf(Pair(500f, 0.6f)),
+ sdrToHdrRatioSpline = mockSpline
+ ))
+ setupHdrLayer(width = 200, height = 200, maxHdrRatio = 8f)
+
+ modifier.setAmbientLux(500f)
+
+ // verify debounce is not scheduled
+ assertThat(testHandler.hasMessagesOrCallbacks()).isFalse()
+
+ val hdrBrightnessFromSdr = 0.83f
+ whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+ 0f, 8f, mockSpline)).thenReturn(hdrBrightnessFromSdr)
+
+ assertModifierState(
+ hdrBrightness = hdrBrightnessFromSdr,
+ spline = mockSpline,
+ hdrRatio = 8f
+ )
+ }
+
+ @Test
+ fun `test ambient lux decrease below maxBrightnessLimits no HDR`() {
+ initHdrModifier()
+ modifier.setAmbientLux(1000f)
+ setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData(
+ maxBrightnessLimits = mapOf(Pair(500f, 0.6f))
+ ))
+
+ modifier.setAmbientLux(499f)
+ // verify debounce is not scheduled
+ assertThat(testHandler.hasMessagesOrCallbacks()).isFalse()
+
+ assertModifierState()
+ verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any())
+ }
+
+ @Test
+ fun `test ambient lux decrease below maxBrightnessLimits with HDR`() {
+ initHdrModifier()
+ modifier.setAmbientLux(1000f)
+ val maxBrightness = 0.6f
+ val brightnessDecreaseDebounceMillis = 2800L
+ val animationRate = 0.01f
+ setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData(
+ maxBrightnessLimits = mapOf(Pair(500f, maxBrightness)),
+ brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis,
+ screenBrightnessRampDecrease = animationRate,
+ sdrToHdrRatioSpline = mockSpline,
+ ))
+ setupHdrLayer(width = 200, height = 200, maxHdrRatio = 8f)
+
+ modifier.setAmbientLux(499f)
+
+ val hdrBrightnessFromSdr = 0.83f
+ whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr(
+ 0f, 8f, mockSpline)).thenReturn(hdrBrightnessFromSdr)
+ // debounce with brightnessDecreaseDebounceMillis, no changes to the state just yet
+ assertModifierState(
+ hdrBrightness = hdrBrightnessFromSdr,
+ spline = mockSpline,
+ hdrRatio = 8f
+ )
+
+ // verify debounce is scheduled
+ assertThat(testHandler.hasMessagesOrCallbacks()).isTrue()
+ val msgInfo = testHandler.pendingMessages.peek()
+ assertSendTime(brightnessDecreaseDebounceMillis, msgInfo!!.sendTime)
+ clearInvocations(mockChangeListener)
+
+ // triggering debounce, state changes
+ testHandler.flush()
+
+ verify(mockChangeListener).onChanged()
+
+ assertModifierState(
+ hdrBrightness = maxBrightness,
+ spline = mockSpline,
+ hdrRatio = 8f,
+ maxBrightness = maxBrightness,
+ animationRate = animationRate
+ )
+ }
+
+ private fun setupHdrLayer(width: Int = 100, height: Int = 100, maxHdrRatio: Float = 0.8f) {
+ testInjector.registeredHdrListener!!.onHdrInfoChanged(
+ mockDisplayBinder, 1, width, height, 0, maxHdrRatio
+ )
+ testHandler.flush()
+ }
+
+ private fun setupDisplay(
+ width: Int = 100,
+ height: Int = 100,
+ hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData(
+ minimumHdrPercentOfScreenForNbm = 0.5f,
+ minimumHdrPercentOfScreenForHbm = 0.7f,
+ transitionPoint = 0.68f,
+ sdrToHdrRatioSpline = mockSpline
+ )
+ ) {
+ whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(hdrBrightnessData)
+ modifier.onDisplayChanged(createDisplayDeviceData(
+ mockDisplayDeviceConfig, mockDisplayBinder,
+ width = width,
+ height = height
+ ))
+ testHandler.flush()
+ }
+
+ private fun initHdrModifier(hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData()) {
+ whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(hdrBrightnessData)
+ modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData)
+ testHandler.flush()
+ }
+
+ // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
+ // (in Handler.sendMessageDelayed) and then by subtracting SystemClock.uptimeMillis()
+ // (in TestHandler.sendMessageAtTime, there might be several milliseconds difference between
+ // SystemClock.uptimeMillis() calls, and subtracted value might be greater than added.
+ private fun assertSendTime(expectedTime: Long, sendTime: Long) {
+ assertThat(sendTime).isAtMost(expectedTime)
+ assertThat(sendTime).isGreaterThan(expectedTime - SEND_TIME_TOLERANCE)
+ }
+
+ private fun assertModifierState(
+ maxBrightness: Float = BRIGHTNESS_MAX,
+ hdrRatio: Float = DEFAULT_MAX_HDR_SDR_RATIO,
+ spline: Spline? = null,
+ hdrBrightness: Float = BRIGHTNESS_NOT_SET,
+ animationRate: Float = CUSTOM_ANIMATION_RATE_NOT_SET
+ ) {
+ val modifierState = ModifiersAggregatedState()
+ modifier.applyStateChange(modifierState)
+
+ assertThat(modifierState.mMaxHdrBrightness).isEqualTo(maxBrightness)
+ assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(hdrRatio)
+ assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(spline)
+
+ val stateBuilder = DisplayBrightnessState.builder()
+ modifier.apply(mockRequest, stateBuilder)
+
+ assertThat(stateBuilder.hdrBrightness).isEqualTo(hdrBrightness)
+ assertThat(stateBuilder.customAnimationRate).isEqualTo(animationRate)
+ }
+
+ internal class TestInjector : Injector() {
+ var registeredHdrListener: SurfaceControlHdrLayerInfoListener? = null
+ var registeredToken: IBinder? = null
+
+ override fun registerHdrListener(
+ listener: SurfaceControlHdrLayerInfoListener, token: IBinder
+ ) {
+ registeredHdrListener = listener
+ registeredToken = token
+ }
+
+ override fun unregisterHdrListener(
+ listener: SurfaceControlHdrLayerInfoListener, token: IBinder
+ ) {
+ registeredHdrListener = null
+ registeredToken = null
+ }
+ }
+} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
index b742d021f2e9..f59e1275d2ce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
@@ -26,6 +26,7 @@ import com.android.server.display.TestUtils
import com.android.server.display.brightness.clamper.LightSensorController.Injector
import com.android.server.display.brightness.clamper.LightSensorController.LightSensorListener
import com.android.server.display.config.SensorData
+import com.android.server.display.config.createSensorData
import com.android.server.display.utils.AmbientFilter
import org.junit.Before
import org.mockito.kotlin.any
@@ -51,7 +52,7 @@ class LightSensorControllerTest {
private val mockAmbientFilter: AmbientFilter = mock()
private val testInjector = TestInjector()
- private val dummySensorData = SensorData()
+ private val dummySensorData = createSensorData()
private lateinit var controller: LightSensorController
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
index 3b3d6f74da50..c7580331c841 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
@@ -24,6 +24,17 @@ import java.io.ByteArrayOutputStream
import java.io.OutputStreamWriter
import org.xmlpull.v1.XmlSerializer
+@JvmOverloads
+fun createSensorData(
+ type: String? = null,
+ name: String? = null,
+ minRefreshRate: Float = 0f,
+ maxRefreshRate: Float = Float.POSITIVE_INFINITY,
+ supportedModes: List<SupportedModeData> = emptyList()
+): SensorData {
+ return SensorData(type, name, minRefreshRate, maxRefreshRate, supportedModes)
+}
+
fun createRefreshRateData(
defaultRefreshRate: Int = 60,
defaultPeakRefreshRate: Int = 60,
@@ -46,6 +57,7 @@ fun createHdrBrightnessData(
screenBrightnessRampIncrease: Float = 0.02f,
brightnessDecreaseDebounceMillis: Long = 3000,
screenBrightnessRampDecrease: Float = 0.04f,
+ transitionPoint: Float = 0.65f,
minimumHdrPercentOfScreenForNbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
minimumHdrPercentOfScreenForHbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
allowInLowPowerMode: Boolean = false,
@@ -57,6 +69,7 @@ fun createHdrBrightnessData(
screenBrightnessRampIncrease,
brightnessDecreaseDebounceMillis,
screenBrightnessRampDecrease,
+ transitionPoint,
minimumHdrPercentOfScreenForNbm,
minimumHdrPercentOfScreenForHbm,
allowInLowPowerMode,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
index 19c6924dfa5b..917c681a0d95 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
@@ -16,6 +16,7 @@
package com.android.server.display.config
+import android.os.PowerManager
import android.util.Spline.createSpline
import androidx.test.filters.SmallTest
import com.android.server.display.DisplayBrightnessState
@@ -42,7 +43,7 @@ class HdrBrightnessDataTest {
)
}
- val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) { 0.6f }
assertThat(hdrBrightnessData).isNotNull()
assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(3000)
@@ -54,6 +55,7 @@ class HdrBrightnessDataTest {
assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(500f, 0.6f)
assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(600f, 0.7f)
+ assertThat(hdrBrightnessData.hbmTransitionPoint).isEqualTo(PowerManager.BRIGHTNESS_MAX)
assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(
HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
)
@@ -79,10 +81,13 @@ class HdrBrightnessDataTest {
)
}
- val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ val transitionPoint = 0.6f
+ val hdrBrightnessData =
+ HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint }
assertThat(hdrBrightnessData).isNotNull()
- assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
+ assertThat(hdrBrightnessData!!.hbmTransitionPoint).isEqualTo(transitionPoint)
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
@@ -100,7 +105,9 @@ class HdrBrightnessDataTest {
)
}
- val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ val transitionPoint = 0.6f
+ val hdrBrightnessData =
+ HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint }
assertThat(hdrBrightnessData).isNotNull()
assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(0)
@@ -112,6 +119,7 @@ class HdrBrightnessDataTest {
assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(0)
+ assertThat(hdrBrightnessData.hbmTransitionPoint).isEqualTo(transitionPoint)
assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
@@ -125,7 +133,7 @@ class HdrBrightnessDataTest {
fun `test HdrBrightnessData configuration no configuration`() {
val displayConfiguration = createDisplayConfiguration()
- val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) { 0.6f }
assertThat(hdrBrightnessData).isNull()
}
@@ -144,10 +152,13 @@ class HdrBrightnessDataTest {
)
}
- val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ val transitionPoint = 0.6f
+ val hdrBrightnessData =
+ HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint }
assertThat(hdrBrightnessData).isNotNull()
- assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f)
+ assertThat(hdrBrightnessData!!.hbmTransitionPoint).isEqualTo(transitionPoint)
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f)
assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.6f)
assertThat(hdrBrightnessData.allowInLowPowerMode).isTrue()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
index 6e2d954c05a8..c0f5e7a69af7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
@@ -16,6 +16,8 @@
package com.android.server.display.utils;
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;
@@ -65,7 +67,7 @@ public class SensorUtilsTest {
@Test
public void testNoSensorManager() {
- Sensor result = SensorUtils.findSensor(null, new SensorData(), Sensor.TYPE_LIGHT);
+ Sensor result = SensorUtils.findSensor(null, createSensorData(), Sensor.TYPE_LIGHT);
assertNull(result);
}
@@ -123,7 +125,7 @@ public class SensorUtilsTest {
when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors);
when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor);
- SensorData sensorData = new SensorData(sensorType, sensorName);
+ SensorData sensorData = createSensorData(sensorType, sensorName);
Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java
index 99968d5117c7..9da695a4effb 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java
@@ -19,10 +19,12 @@ package com.android.server.dreams;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.content.res.Resources;
@@ -44,9 +46,6 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
-import java.util.Collections;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DreamAccessibilityTest {
@@ -73,7 +72,8 @@ public class DreamAccessibilityTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDreamAccessibility = new DreamAccessibility(mContext, mView);
+ Runnable mDismissCallback = () -> {};
+ mDreamAccessibility = new DreamAccessibility(mContext, mView, mDismissCallback);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getString(R.string.dream_accessibility_action_click))
@@ -84,80 +84,55 @@ public class DreamAccessibilityTest {
*/
@Test
public void testConfigureAccessibilityActions() {
- when(mAccessibilityNodeInfo.getActionList()).thenReturn(new ArrayList<>());
+ when(mView.getAccessibilityDelegate()).thenReturn(null);
- mDreamAccessibility.updateAccessibilityConfiguration(false);
+ mDreamAccessibility.updateAccessibilityConfiguration();
verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
- View.AccessibilityDelegate capturedDelegate =
- mAccessibilityDelegateArgumentCaptor.getValue();
+ View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor
+ .getValue();
capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo);
verify(mAccessibilityNodeInfo).addAction(argThat(action ->
- action.getId() == AccessibilityNodeInfo.ACTION_CLICK
+ action.getId() == AccessibilityNodeInfo.ACTION_DISMISS
&& TextUtils.equals(action.getLabel(), CUSTOM_ACTION)));
}
/**
- * Test to verify the configuration of accessibility actions within a view delegate,
- * specifically checking the removal of an existing click action and addition
- * of a new custom action.
+ * Test to verify no accessibility configuration is added if one exist.
*/
@Test
- public void testConfigureAccessibilityActions_RemovesExistingClickAction() {
- AccessibilityNodeInfo.AccessibilityAction existingAction =
- new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
- EXISTING_ACTION);
- when(mAccessibilityNodeInfo.getActionList())
- .thenReturn(Collections.singletonList(existingAction));
-
- mDreamAccessibility.updateAccessibilityConfiguration(false);
-
- verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
- View.AccessibilityDelegate capturedDelegate =
- mAccessibilityDelegateArgumentCaptor.getValue();
-
- capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo);
+ public void testNotAddingDuplicateAccessibilityConfiguration() {
+ View.AccessibilityDelegate existingDelegate = mock(View.AccessibilityDelegate.class);
+ when(mView.getAccessibilityDelegate()).thenReturn(existingDelegate);
- verify(mAccessibilityNodeInfo).removeAction(existingAction);
- verify(mAccessibilityNodeInfo).addAction(argThat(action ->
- action.getId() == AccessibilityNodeInfo.ACTION_CLICK
- && TextUtils.equals(action.getLabel(), CUSTOM_ACTION)));
+ mDreamAccessibility.updateAccessibilityConfiguration();
+ verify(mView, never()).setAccessibilityDelegate(any());
}
/**
- * Test to verify the removal of a custom accessibility action within a view delegate.
+ * Test to verify dismiss callback is called
*/
@Test
- public void testRemoveCustomAccessibilityAction() {
+ public void testPerformAccessibilityAction() {
+ Runnable mockDismissCallback = mock(Runnable.class);
+ DreamAccessibility dreamAccessibility = new DreamAccessibility(mContext,
+ mView, mockDismissCallback);
- AccessibilityNodeInfo.AccessibilityAction existingAction =
- new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
- EXISTING_ACTION);
- when(mAccessibilityNodeInfo.getActionList())
- .thenReturn(Collections.singletonList(existingAction));
+ dreamAccessibility.updateAccessibilityConfiguration();
- mDreamAccessibility.updateAccessibilityConfiguration(false);
verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture());
- View.AccessibilityDelegate capturedDelegate =
- mAccessibilityDelegateArgumentCaptor.getValue();
- when(mView.getAccessibilityDelegate()).thenReturn(capturedDelegate);
- clearInvocations(mView);
+ View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor
+ .getValue();
- mDreamAccessibility.updateAccessibilityConfiguration(true);
- verify(mView).setAccessibilityDelegate(null);
- }
+ boolean result = capturedDelegate.performAccessibilityAction(mView,
+ AccessibilityNodeInfo.ACTION_DISMISS, null);
- /**
- * Test to verify the removal of custom accessibility action is not called if delegate is not
- * set by the dreamService.
- */
- @Test
- public void testRemoveCustomAccessibility_DoesNotRemoveDelegateNotSetByDreamAccessibility() {
- mDreamAccessibility.updateAccessibilityConfiguration(true);
- verify(mView, never()).setAccessibilityDelegate(any());
+ assertTrue(result);
+ verify(mockDismissCallback).run();
}
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
index 72883e269a65..5bd919f28e6a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -23,6 +23,7 @@ import static com.android.server.alarm.UserWakeupStore.USER_START_TIME_DEVIATION
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.testng.AssertJUnit.assertFalse;
import android.os.Environment;
import android.os.FileUtils;
@@ -51,6 +52,7 @@ public class UserWakeupStoreTest {
private static final int USER_ID_1 = 10;
private static final int USER_ID_2 = 11;
private static final int USER_ID_3 = 12;
+ private static final int USER_ID_SYSTEM = 0;
private static final long TEST_TIMESTAMP = 150_000;
private static final File TEST_SYSTEM_DIR = new File(InstrumentationRegistry
.getInstrumentation().getContext().getDataDir(), "alarmsTestDir");
@@ -110,6 +112,14 @@ public class UserWakeupStoreTest {
}
@Test
+ public void testAddWakeupForSystemUser_shouldDoNothing() {
+ mUserWakeupStore.addUserWakeup(USER_ID_SYSTEM, TEST_TIMESTAMP - 19_000);
+ assertEquals(0, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ final File file = new File(ROOT_DIR , "usersWithAlarmClocks.xml");
+ assertFalse(file.exists());
+ }
+
+ @Test
public void testAddMultipleWakeupsForUser_ensureOnlyLastWakeupRemains() {
final long finalAlarmTime = TEST_TIMESTAMP - 13_000;
mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 29_000);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1dbd5320cac6..8656b991b5fc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -500,6 +500,13 @@ public class MockingOomAdjusterTests {
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
assertEquals("resumed-split-screen-activity", app.mState.getAdjType());
+
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE
+ | WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM)
+ .when(wpc).getActivityStateFlags();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
+ assertEquals("perceptible-freeform-activity", app.mState.getAdjType());
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f2b4136c51ed..b2a5b02c49e1 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -59,6 +59,7 @@ android_ravenwood_test {
name: "PowerStatsTestsRavenwood",
static_libs: [
"services.core",
+ "platformprotosnano",
"coretests-aidl",
"ravenwood-junit",
"truth",
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index ac1f7d0e345f..37d8f2f74850 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.os;
+package com.android.server.power.stats;
import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE;
@@ -23,39 +23,262 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
+import android.os.Process;
import android.os.UidBatteryConsumer;
import android.os.nano.BatteryUsageStatsAtomsProto;
import android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.StatsEvent;
import androidx.test.filters.SmallTest;
+import com.android.server.am.BatteryStatsService;
+
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import org.junit.Rule;
import org.junit.Test;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-
@SmallTest
-public class BatteryUsageStatsPulledTest {
+public class BatteryUsageStatsAtomTest {
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
private static final int UID_0 = 1000;
private static final int UID_1 = 2000;
private static final int UID_2 = 3000;
private static final int UID_3 = 4000;
- private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
- BatteryConsumer.PROCESS_STATE_FOREGROUND,
- BatteryConsumer.PROCESS_STATE_BACKGROUND,
- BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE
- };
@Test
- public void testGetStatsProto() {
+ public void testAtom_BatteryUsageStatsPerUid() {
+ final BatteryUsageStats bus = buildBatteryUsageStats();
+ BatteryStatsService.FrameworkStatsLogger statsLogger =
+ mock(BatteryStatsService.FrameworkStatsLogger.class);
+
+ List<StatsEvent> actual = new ArrayList<>();
+ new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual);
+
+ // Device-wide totals
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
+ "cpu",
+ 30000.0f,
+ 20100.0f,
+ 20300L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
+ "camera",
+ 30000.0f,
+ 20150.0f,
+ 0L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
+ "CustomConsumer1",
+ 30000.0f,
+ 20200.0f,
+ 20400L
+ );
+
+ // Per-proc state estimates for UID_0
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
+ "screen",
+ 1650.0f,
+ 300.0f,
+ 0L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
+ "cpu",
+ 1650.0f,
+ 400.0f,
+ 600L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ 1000L,
+ "cpu",
+ 1650.0f,
+ 9100.0f,
+ 8100L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ 2000L,
+ "cpu",
+ 1650.0f,
+ 9200.0f,
+ 8200L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ 0L,
+ "cpu",
+ 1650.0f,
+ 9300.0f,
+ 8400L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_CACHED,
+ 0L,
+ "cpu",
+ 1650.0f,
+ 9400.0f,
+ 0L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ 1000L,
+ "CustomConsumer1",
+ 1650.0f,
+ 450.0f,
+ 0L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ 2000L,
+ "CustomConsumer1",
+ 1650.0f,
+ 450.0f,
+ 0L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ 1000L,
+ "CustomConsumer2",
+ 1650.0f,
+ 500.0f,
+ 800L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ 2000L,
+ "CustomConsumer2",
+ 1650.0f,
+ 500.0f,
+ 800L
+ );
+
+ // Nothing for UID_1, because its power consumption is 0
+
+ // Only "screen" is populated for UID_2
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_2,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
+ "screen",
+ 766.0f,
+ 766.0f,
+ 0L
+ );
+
+ verifyNoMoreInteractions(statsLogger);
+ }
+
+ @Test
+ public void testAtom_BatteryUsageStatsAtomsProto() {
final BatteryUsageStats bus = buildBatteryUsageStats();
final byte[] bytes = bus.getStatsProto();
BatteryUsageStatsAtomsProto proto;
@@ -68,9 +291,7 @@ public class BatteryUsageStatsPulledTest {
assertEquals(bus.getStatsStartTimestamp(), proto.sessionStartMillis);
assertEquals(bus.getStatsEndTimestamp(), proto.sessionEndMillis);
- assertEquals(
- bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(),
- proto.sessionDurationMillis);
+ assertEquals(10000, proto.sessionDurationMillis);
assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage);
assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis);
@@ -90,8 +311,8 @@ public class BatteryUsageStatsPulledTest {
final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
uidConsumers.sort((a, b) -> a.getUid() - b.getUid());
- final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto
- = proto.uidBatteryConsumers;
+ final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto =
+ proto.uidBatteryConsumers;
Arrays.sort(uidConsumersProto, (a, b) -> a.uid - b.uid);
// UID_0 - After sorting, UID_0 should be in position 0 for both data structures
@@ -186,6 +407,12 @@ public class BatteryUsageStatsPulledTest {
}
}
+ private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE
+ };
+
private void assertSameUidBatteryConsumer(
android.os.UidBatteryConsumer uidConsumer,
BatteryUsageStatsAtomsProto.UidBatteryConsumer uidConsumerProto,
@@ -195,10 +422,10 @@ public class BatteryUsageStatsPulledTest {
assertEquals("Uid consumers had mismatched uids", uid, uidConsumer.getUid());
assertEquals("For uid " + uid,
- uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND),
+ uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND),
uidConsumerProto.timeInForegroundMillis);
assertEquals("For uid " + uid,
- uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND),
+ uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND),
uidConsumerProto.timeInBackgroundMillis);
for (int processState : UID_USAGE_TIME_PROCESS_STATES) {
final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState);
@@ -261,11 +488,15 @@ public class BatteryUsageStatsPulledTest {
new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
/* includePowerModels */ true,
/* includeProcessStats */ true,
+ /* includeScreenStateData */ false,
+ /* includePowerStateData */ false,
/* minConsumedPowerThreshold */ 0)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
.setDischargeDurationMs(1234)
- .setStatsStartTimestamp(1000);
+ .setStatsStartTimestamp(1000)
+ .setStatsEndTimestamp(20000)
+ .setStatsDuration(10000);
final UidBatteryConsumer.Builder uidBuilder = builder
.getOrCreateUidBatteryConsumerBuilder(UID_0)
.setPackageWithHighestDrain("myPackage0")
@@ -345,7 +576,7 @@ public class BatteryUsageStatsPulledTest {
@Test
public void testLargeAtomTruncated() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[0], true, false, 0);
+ new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
// If not truncated, this BatteryUsageStats object would generate a proto buffer
// significantly larger than 50 Kb
for (int i = 0; i < 3000; i++) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 6edfedee9e5b..624b18948c49 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -397,10 +397,14 @@ public class BatteryUsageStatsRule implements TestRule {
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
+ final boolean includeScreenStateData = (query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0;
+ final boolean includePowerStateData = (query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
customPowerComponentNames, includePowerModels, includeProcessStateData,
- minConsumedPowerThreshold);
+ includeScreenStateData, includePowerStateData, minConsumedPowerThreshold);
SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
for (int i = 0; i < uidStats.size(); i++) {
builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index a3f0770ec8ba..52bb5e839ca2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -31,6 +31,8 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static java.util.regex.Pattern.quote;
+
import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
@@ -91,7 +93,7 @@ public class BatteryUsageStatsTest {
final Parcel parcel = Parcel.obtain();
parcel.writeParcelable(outBatteryUsageStats, 0);
- assertThat(parcel.dataSize()).isLessThan(12000);
+ assertThat(parcel.dataSize()).isLessThan(100000);
parcel.setDataPosition(0);
@@ -161,15 +163,47 @@ public class BatteryUsageStatsTest {
assertThat(dump).contains("Computed drain: 30000");
assertThat(dump).contains("actual drain: 1000-2000");
assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
- assertThat(dump).contains("cpu(fg): 2333 apps: 1333 duration: 3s 332ms");
- assertThat(dump).contains("cpu(bg): 2444 apps: 1444 duration: 4s 442ms");
- assertThat(dump).contains("cpu(fgs): 2555 apps: 1555 duration: 5s 552ms");
- assertThat(dump).contains("cpu(cached): 123 apps: 123 duration: 456ms");
assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
- assertThat(dump).contains("UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123 "
- + "( screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) cpu:bg=1888 (8s 881ms) "
- + "cpu:fgs=1999 (9s 991ms) cpu:cached=123 (456ms) FOO=500 )");
- assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 (30ms) FOO=20.0 )");
+ assertThat(dump).containsMatch(quote("(on battery, screen on)") + "\\s*"
+ + "cpu: 2333 apps: 1333 duration: 3s 332ms");
+ assertThat(dump).containsMatch(quote("(not on battery, screen on)") + "\\s*"
+ + "cpu: 2555 apps: 1555 duration: 5s 552ms");
+ assertThat(dump).containsMatch(quote("(on battery, screen off/doze)") + "\\s*"
+ + "cpu: 2444 apps: 1444 duration: 4s 442ms");
+ assertThat(dump).containsMatch(quote("(not on battery, screen off/doze)") + "\\s*"
+ + "cpu: 123 apps: 123 duration: 456ms");
+ assertThat(dump).containsMatch(
+ "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*"
+ + quote("screen=300 cpu=5787 (27s 99ms) cpu:fg=1777 (7s 771ms) "
+ + "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
+ + "cpu:cached=123 (456ms) FOO=500") + "\\s*"
+ + quote("(on battery, screen on)") + "\\s*"
+ + quote("cpu:fg=1777 (7s 771ms)"));
+ assertThat(dump).containsMatch("User 42: 30.0\\s*"
+ + quote("cpu=10.0 (30ms) FOO=20.0"));
+ }
+
+ @Test
+ public void testDumpNoScreenOrPowerState() {
+ final BatteryUsageStats stats = buildBatteryUsageStats1(true, false, false).build();
+ final StringWriter out = new StringWriter();
+ try (PrintWriter pw = new PrintWriter(out)) {
+ stats.dump(pw, " ");
+ }
+ final String dump = out.toString();
+
+ assertThat(dump).contains("Capacity: 4000");
+ assertThat(dump).contains("Computed drain: 30000");
+ assertThat(dump).contains("actual drain: 1000-2000");
+ assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
+ assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
+ assertThat(dump).containsMatch(
+ "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*"
+ + quote("screen=300 cpu=5787 (600ms) cpu:fg=1777 (7s 771ms) "
+ + "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
+ + "cpu:cached=123 (456ms) FOO=500"));
+ assertThat(dump).containsMatch("User 42: 30.0\\s*"
+ + quote("cpu=10.0 (30ms) FOO=20.0"));
}
@Test
@@ -186,9 +220,8 @@ public class BatteryUsageStatsTest {
public void testAdd() {
final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
-
final BatteryUsageStats sum =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0)
.add(stats1)
.add(stats2)
.build();
@@ -200,14 +233,14 @@ public class BatteryUsageStatsTest {
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 2124, null,
- 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745,
+ 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 11772,
POWER_MODEL_UNDEFINED,
956, 1167, 1478,
true, 3554, 3776, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
444, 1110);
} else if (uidBatteryConsumer.getUid() == APP_UID2) {
assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
- 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
+ 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5985,
BatteryConsumer.POWER_MODEL_POWER_PROFILE,
555, 666, 777,
true, 1777, 1888, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
@@ -229,7 +262,7 @@ public class BatteryUsageStatsTest {
@Test
public void testAdd_customComponentMismatch() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -238,7 +271,7 @@ public class BatteryUsageStatsTest {
@Test
public void testAdd_processStateDataMismatch() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -259,15 +292,23 @@ public class BatteryUsageStatsTest {
parser.setInput(in, StandardCharsets.UTF_8.name());
final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
+ System.out.println("stats = " + stats);
+ System.out.println("fromXml = " + fromXml);
assertBatteryUsageStats1(fromXml, true);
}
private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) {
+ return buildBatteryUsageStats1(includeUserBatteryConsumer, true, true);
+ }
+
+ private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer,
+ boolean includeScreenState, boolean includePowerState) {
final MockClock clocks = new MockClock();
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true,
+ includeScreenState, includePowerState, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
@@ -312,7 +353,7 @@ public class BatteryUsageStatsTest {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(customPowerComponentNames, true,
- includeProcessStateData, 0);
+ includeProcessStateData, true, true, 0);
builder.setDischargePercentage(30)
.setDischargedPowerRange(1234, 2345)
.setStatsStartTimestamp(2000)
@@ -371,9 +412,15 @@ public class BatteryUsageStatsTest {
.setUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
if (builder.isProcessStateDataNeeded()) {
- final BatteryConsumer.Key cpuFgKey = uidBuilder.getKey(
- BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded()
+ ? uidBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_BATTERY)
+ : uidBuilder.getKey(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
final BatteryConsumer.Key cpuBgKey = uidBuilder.getKey(
BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
@@ -401,9 +448,9 @@ public class BatteryUsageStatsTest {
private void addAggregateBatteryConsumer(BatteryUsageStats.Builder builder, int scope,
double consumedPower, int cpuPower, int customComponentPower, int cpuDuration,
- int customComponentDuration, double cpuPowerForeground, long cpuDurationForeground,
- double cpuPowerBackground, long cpuDurationBackground, double cpuPowerFgs,
- long cpuDurationFgs, double cpuPowerCached, long cpuDurationCached) {
+ int customComponentDuration, double cpuPowerBatScrOn, long cpuDurationBatScrOn,
+ double cpuPowerBatScrOff, long cpuDurationBatScrOff, double cpuPowerChgScrOn,
+ long cpuDurationChgScrOn, double cpuPowerChgScrOff, long cpuDurationChgScrOff) {
final AggregateBatteryConsumer.Builder aggBuilder =
builder.getAggregateBatteryConsumerBuilder(scope)
.setConsumedPower(consumedPower)
@@ -417,32 +464,40 @@ public class BatteryUsageStatsTest {
.setUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
customComponentDuration);
- if (builder.isProcessStateDataNeeded()) {
- final BatteryConsumer.Key cpuFgKey = aggBuilder.getKey(
+ if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) {
+ final BatteryConsumer.Key cpuBatScrOn = aggBuilder.getKey(
BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND);
- final BatteryConsumer.Key cpuBgKey = aggBuilder.getKey(
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_BATTERY);
+ final BatteryConsumer.Key cpuBatScrOff = aggBuilder.getKey(
BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_BACKGROUND);
- final BatteryConsumer.Key cpuFgsKey = aggBuilder.getKey(
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_BATTERY);
+ final BatteryConsumer.Key cpuChgScrOn = aggBuilder.getKey(
BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
- final BatteryConsumer.Key cpuCachedKey = aggBuilder.getKey(
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_OTHER);
+ final BatteryConsumer.Key cpuChgScrOff = aggBuilder.getKey(
BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_CACHED);
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_OTHER);
aggBuilder
- .setConsumedPower(cpuFgKey, cpuPowerForeground,
+ .setConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuFgKey, cpuDurationForeground)
- .setConsumedPower(cpuBgKey, cpuPowerBackground,
+ .setUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
+ .setConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuBgKey, cpuDurationBackground)
- .setConsumedPower(cpuFgsKey, cpuPowerFgs,
+ .setUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
+ .setConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
- .setConsumedPower(cpuCachedKey, cpuPowerCached,
+ .setUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
+ .setConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuCachedKey, cpuDurationCached);
+ .setUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
}
}
@@ -456,7 +511,7 @@ public class BatteryUsageStatsTest {
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
- 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
+ 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5787,
BatteryConsumer.POWER_MODEL_POWER_PROFILE,
500, 600, 800,
true, 1777, 1888, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
@@ -568,54 +623,53 @@ public class BatteryUsageStatsTest {
.isEqualTo(totalPowerCached);
}
- final BatteryConsumer.Key cpuFgKey = uidBatteryConsumer.getKey(
+ final BatteryConsumer.Dimensions cpuFg = new BatteryConsumer.Dimensions(
BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_FOREGROUND);
if (processStateDataIncluded) {
- assertThat(cpuFgKey).isNotNull();
- assertThat(uidBatteryConsumer.getConsumedPower(cpuFgKey))
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuFg))
.isEqualTo(cpuPowerForeground);
- assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgKey))
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFg))
.isEqualTo(cpuDurationForeground);
} else {
- assertThat(cpuFgKey).isNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuFg)).isEqualTo(0);
}
- final BatteryConsumer.Key cpuBgKey = uidBatteryConsumer.getKey(
+ final BatteryConsumer.Dimensions cpuBg = new BatteryConsumer.Dimensions(
BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_BACKGROUND);
if (processStateDataIncluded) {
- assertThat(cpuBgKey).isNotNull();
- assertThat(uidBatteryConsumer.getConsumedPower(cpuBgKey))
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuBg))
.isEqualTo(cpuPowerBackground);
- assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBgKey))
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBg))
.isEqualTo(cpuDurationBackground);
} else {
- assertThat(cpuBgKey).isNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuBg))
+ .isEqualTo(0);
}
- final BatteryConsumer.Key cpuFgsKey = uidBatteryConsumer.getKey(
+ final BatteryConsumer.Dimensions cpuFgs = new BatteryConsumer.Dimensions(
BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
if (processStateDataIncluded) {
- assertThat(cpuFgsKey).isNotNull();
- assertThat(uidBatteryConsumer.getConsumedPower(cpuFgsKey))
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuFgs))
.isEqualTo(cpuPowerFgs);
- assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgsKey))
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgs))
.isEqualTo(cpuDurationFgs);
} else {
- assertThat(cpuFgsKey).isNotNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cpuFgs))
+ .isEqualTo(0);
}
- final BatteryConsumer.Key cachedKey = uidBatteryConsumer.getKey(
+ final BatteryConsumer.Dimensions cached = new BatteryConsumer.Dimensions(
BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_CACHED);
if (processStateDataIncluded) {
- assertThat(cachedKey).isNotNull();
- assertThat(uidBatteryConsumer.getConsumedPower(cachedKey))
+ assertThat(uidBatteryConsumer.getConsumedPower(cached))
.isEqualTo(cpuPowerCached);
- assertThat(uidBatteryConsumer.getUsageDurationMillis(cachedKey))
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(cached))
.isEqualTo(cpuDurationCached);
} else {
- assertThat(cpuFgsKey).isNotNull();
+ assertThat(uidBatteryConsumer.getConsumedPower(cached))
+ .isEqualTo(0);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 644ae4717eb1..005ceee703a8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -130,7 +130,7 @@ public class CpuPowerStatsCollectorValidationTest {
boolean inCpuSection = false;
for (int i = 0; i < lines.length; i++) {
if (!inCpuSection) {
- if (lines[i].startsWith("CpuPowerStatsCollector")) {
+ if (lines[i].startsWith("cpu (1)")) {
inCpuSection = true;
}
} else if (lines[i].startsWith(" ")) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
index 7bec13f653e7..1621d47d62b5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
@@ -149,9 +149,9 @@ public class CustomEnergyConsumerPowerStatsTest {
.isEqualTo(20000);
assertThat(ps2.uidStats.size()).isEqualTo(2);
assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID1), 0))
- .isEqualTo(14000);
+ .isEqualTo(4000);
assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID2), 0))
- .isEqualTo(21000);
+ .isEqualTo(6000);
}
@Test
@@ -209,8 +209,8 @@ public class CustomEnergyConsumerPowerStatsTest {
assertThat(POWER_STATS_LAYOUT.getDevicePowerEstimate(deviceStats))
.isWithin(PRECISION).of(expectedPower * 0.75);
- // UID1: estimated power = 14,000 uC = 0.00388 mAh
- expectedPower = 0.00388;
+ // UID1: estimated power = 4,000 uC = 0.00111 mAh
+ expectedPower = 0.00111;
ps2.getUidStats(uidStats, APP_UID1,
states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats))
@@ -221,8 +221,8 @@ public class CustomEnergyConsumerPowerStatsTest {
assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats))
.isWithin(PRECISION).of(expectedPower * 0.75);
- // UID2: estimated power = 21,000 uC = 0.00583 mAh
- expectedPower = 0.00583;
+ // UID2: estimated power = 6,000 uC = 0.00166 mAh
+ expectedPower = 0.00167;
ps2.getUidStats(uidStats, APP_UID2,
states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
index 32bfb2cd3507..7f7967ba4d7b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -19,7 +19,6 @@ package com.android.server.power.stats;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import android.os.AggregateBatteryConsumer;
@@ -131,9 +130,20 @@ public class PowerStatsExporterTest {
@Test
public void breakdownByProcState_fullRange() throws Exception {
+ breakdownByProcState_fullRange(false, false);
+ }
+
+ @Test
+ public void breakdownByProcStateScreenAndPower_fullRange() throws Exception {
+ breakdownByProcState_fullRange(true, true);
+ }
+
+ private void breakdownByProcState_fullRange(boolean includeScreenStateData,
+ boolean includePowerStateData) throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
new String[]{"cu570m"}, /* includePowerModels */ false,
- /* includeProcessStateData */ true, /* powerThreshold */ 0);
+ /* includeProcessStateData */ true, includeScreenStateData,
+ includePowerStateData, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 1000, 10000);
BatteryUsageStats actual = builder.build();
@@ -177,7 +187,7 @@ public class PowerStatsExporterTest {
public void breakdownByProcState_subRange() throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
new String[]{"cu570m"}, /* includePowerModels */ false,
- /* includeProcessStateData */ true, /* powerThreshold */ 0);
+ /* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 3700, 6700);
BatteryUsageStats actual = builder.build();
@@ -209,7 +219,7 @@ public class PowerStatsExporterTest {
public void combinedProcessStates() throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
new String[]{"cu570m"}, /* includePowerModels */ false,
- /* includeProcessStateData */ false, /* powerThreshold */ 0);
+ /* includeProcessStateData */ false, true, true, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 1000, 10000);
BatteryUsageStats actual = builder.build();
@@ -229,13 +239,13 @@ public class PowerStatsExporterTest {
UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream()
.filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null);
// There shouldn't be any per-procstate data
- assertThrows(
- IllegalArgumentException.class,
- () -> uidScope.getConsumedPower(new BatteryConsumer.Dimensions(
+ for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) {
+ if (procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ assertThat(uidScope.getConsumedPower(new BatteryConsumer.Dimensions(
BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND)));
-
-
+ BatteryConsumer.PROCESS_STATE_FOREGROUND))).isEqualTo(0);
+ }
+ }
actual.close();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index 758c84a26dcd..ef9580c54de6 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@ public class AbsoluteVolumeBehaviorTest {
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 2cb02bdd2806..464515632997 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@ public class AudioDeviceVolumeManagerTest {
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 037c3c00443c..b7100ea00a40 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@ public class AudioServiceTest {
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
+ mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 27b552fa7cdd..746645a8c585 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@ public class DeviceVolumeBehaviorTest {
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class));
+ mock(AudioServerPermissionProvider.class), r -> r.run());
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 8e34ee1b6a42..e45ab319146c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@ public class VolumeHelperTest {
@NonNull PermissionEnforcer enforcer,
AudioServerPermissionProvider permissionProvider) {
super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, appOps, enforcer, permissionProvider);
+ audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
}
public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java b/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java
new file mode 100644
index 000000000000..f698bea1a199
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.autofill;
+
+import static com.android.server.autofill.Helper.SaveInfoStats;
+import static com.android.server.autofill.Helper.getSaveInfoStatsFromFillResponses;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.util.SparseArray;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class HelperTest {
+
+ @Test
+ public void testGetSaveInfoStatsFromFillResponses_nullFillResponses() {
+ SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(null);
+
+ assertThat(saveInfoStats.saveInfoCount).isEqualTo(-1);
+ assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(-1);
+ }
+
+ @Test
+ public void testGetSaveInfoStatsFromFillResponses_emptyFillResponseSparseArray() {
+ SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(new SparseArray<>());
+
+ assertThat(saveInfoStats.saveInfoCount).isEqualTo(0);
+ assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetSaveInfoStatsFromFillResponses_singleResponseWithoutSaveInfo() {
+ FillResponse.Builder fillResponseBuilder = new FillResponse.Builder();
+ // Add client state to satisfy the sanity check in FillResponseBuilder.build()
+ Bundle clientState = new Bundle();
+ fillResponseBuilder.setClientState(clientState);
+ FillResponse testFillResponse = fillResponseBuilder.build();
+
+ SparseArray<FillResponse> testFillResponses = new SparseArray<>();
+ testFillResponses.put(0, testFillResponse);
+
+ SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses);
+
+ assertThat(saveInfoStats.saveInfoCount).isEqualTo(0);
+ assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetSaveInfoStatsFromFillResponses_singleResponseWithSaveInfo() {
+ FillResponse.Builder fillResponseBuilder = new FillResponse.Builder();
+ SaveInfo.Builder saveInfoBuilder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC);
+ fillResponseBuilder.setSaveInfo(saveInfoBuilder.build());
+ FillResponse testFillResponse = fillResponseBuilder.build();
+
+ SparseArray<FillResponse> testFillResponses = new SparseArray<>();
+ testFillResponses.put(0, testFillResponse);
+
+ SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses);
+
+ assertThat(saveInfoStats.saveInfoCount).isEqualTo(1);
+ assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testGetSaveInfoStatsFromFillResponses_multipleResponseWithDifferentTypeSaveInfo() {
+ FillResponse.Builder fillResponseBuilder1 = new FillResponse.Builder();
+ SaveInfo.Builder saveInfoBuilder1 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC);
+ fillResponseBuilder1.setSaveInfo(saveInfoBuilder1.build());
+ FillResponse testFillResponse1 = fillResponseBuilder1.build();
+
+ FillResponse.Builder fillResponseBuilder2 = new FillResponse.Builder();
+ SaveInfo.Builder saveInfoBuilder2 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS);
+ fillResponseBuilder2.setSaveInfo(saveInfoBuilder2.build());
+ FillResponse testFillResponse2 = fillResponseBuilder2.build();
+
+ FillResponse.Builder fillResponseBuilder3 = new FillResponse.Builder();
+ SaveInfo.Builder saveInfoBuilder3 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS);
+ fillResponseBuilder3.setSaveInfo(saveInfoBuilder3.build());
+ FillResponse testFillResponse3 = fillResponseBuilder3.build();
+
+ SparseArray<FillResponse> testFillResponses = new SparseArray<>();
+ testFillResponses.put(0, testFillResponse1);
+ testFillResponses.put(1, testFillResponse2);
+ testFillResponses.put(2, testFillResponse3);
+
+ SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses);
+
+ // Save info count is 3. Since two save info share the same save data type, the distinct
+ // save data type count is 2.
+ assertThat(saveInfoStats.saveInfoCount).isEqualTo(3);
+ assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(2);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 0f385325e525..a4222ff5650b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1518,7 +1518,8 @@ public class BiometricServiceTest {
mBiometricService.onStart();
when(mTrustManager.isInSignificantPlace()).thenReturn(false);
- when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+ when(mBiometricService.mSettingObserver
+ .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt()))
.thenReturn(true);
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
@@ -1540,7 +1541,8 @@ public class BiometricServiceTest {
mBiometricService.onStart();
when(mTrustManager.isInSignificantPlace()).thenReturn(false);
- when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+ when(mBiometricService.mSettingObserver
+ .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt()))
.thenReturn(true);
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
@@ -1564,7 +1566,8 @@ public class BiometricServiceTest {
mBiometricService.onStart();
when(mTrustManager.isInSignificantPlace()).thenReturn(false);
- when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+ when(mBiometricService.mSettingObserver
+ .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt()))
.thenReturn(true);
setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index b831ef5c30bc..240da9fe46bd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -90,7 +90,8 @@ public class PreAuthInfoTest {
when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
.thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
- when(mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())).thenReturn(true);
+ when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ anyInt())).thenReturn(true);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 8a9538f2374a..ebdde94237eb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -66,7 +66,7 @@ public class NetworkEventTest extends DpmTestBase {
any(UserHandle.class));
mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext);
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
- mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE);
+ mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE, null);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 316b5faf2a68..689b241f0faa 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -364,6 +365,39 @@ public class MediaProjectionManagerServiceTest {
@EnableFlags(android.companion.virtualdevice.flags
.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
+ public void testReuseProjection_keyguardNotLocked_startConsentDialog()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+
+ doNothing().when(mContext).startActivityAsUser(any(), any());
+ doReturn(false).when(mKeyguardManager).isKeyguardLocked();
+
+ MediaProjectionManagerService.BinderService mediaProjectionBinderService =
+ mService.new BinderService(mContext);
+ mediaProjectionBinderService.requestConsentForInvalidProjection(projection);
+
+ verify(mContext).startActivityAsUser(any(), any());
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ MediaProjectionManagerService.BinderService mediaProjectionBinderService =
+ mService.new BinderService(mContext);
+ mediaProjectionBinderService.requestConsentForInvalidProjection(projection);
+
+ verify(mContext, never()).startActivityAsUser(any(), any());
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
public void testKeyguardLocked_stopsActiveProjection() throws Exception {
MediaProjectionManagerService service =
new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 54d11387752c..cbf79356e9c4 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -16,7 +16,6 @@
package com.android.server.webkit;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -66,10 +65,12 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
- public String getUserChosenWebViewProvider(Context context) { return mUserProvider; }
+ public String getUserChosenWebViewProvider() {
+ return mUserProvider;
+ }
@Override
- public void updateUserSetting(Context context, String newProviderName) {
+ public void updateUserSetting(String newProviderName) {
mUserProvider = newProviderName;
}
@@ -77,14 +78,14 @@ public class TestSystemImpl implements SystemInterface {
public void killPackageDependents(String packageName) {}
@Override
- public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
+ public void enablePackageForAllUsers(String packageName, boolean enable) {
for(int userId : mUsers) {
enablePackageForUser(packageName, enable, userId);
}
}
@Override
- public void installExistingPackageForAllUsers(Context context, String packageName) {
+ public void installExistingPackageForAllUsers(String packageName) {
for (int userId : mUsers) {
installPackageForUser(packageName, userId);
}
@@ -131,8 +132,7 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
- public List<UserPackage> getPackageInfoForProviderAllUsers(
- Context context, WebViewProviderInfo info) {
+ public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo info) {
Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
List<UserPackage> ret = new ArrayList();
// Loop over defined users, and find the corresponding package for each user.
@@ -185,12 +185,12 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
- public int getMultiProcessSetting(Context context) {
+ public int getMultiProcessSetting() {
return mMultiProcessSetting;
}
@Override
- public void setMultiProcessSetting(Context context, int value) {
+ public void setMultiProcessSetting(int value) {
mMultiProcessSetting = value;
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index e181a513b637..06479c84bfc7 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -104,10 +104,10 @@ public class WebViewUpdateServiceTest {
mTestSystemImpl = Mockito.spy(testing);
if (updateServiceV2()) {
mWebViewUpdateServiceImpl =
- new WebViewUpdateServiceImpl2(null /*Context*/, mTestSystemImpl);
+ new WebViewUpdateServiceImpl2(mTestSystemImpl);
} else {
mWebViewUpdateServiceImpl =
- new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl);
+ new WebViewUpdateServiceImpl(mTestSystemImpl);
}
}
@@ -140,7 +140,7 @@ public class WebViewUpdateServiceTest {
WebViewProviderInfo[] webviewPackages, int numRelros, String userSetting) {
setupWithPackagesAndRelroCount(webviewPackages, numRelros);
if (userSetting != null) {
- mTestSystemImpl.updateUserSetting(null, userSetting);
+ mTestSystemImpl.updateUserSetting(userSetting);
}
// Add (enabled and valid) package infos for each provider
setEnabledAndValidPackageInfos(webviewPackages);
@@ -313,7 +313,7 @@ public class WebViewUpdateServiceTest {
};
setupWithPackagesNonDebuggable(packages);
// Start with the setting pointing to the invalid package
- mTestSystemImpl.updateUserSetting(null, invalidPackage);
+ mTestSystemImpl.updateUserSetting(invalidPackage);
mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */,
true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature}
, 0 /* updateTime */));
@@ -481,7 +481,7 @@ public class WebViewUpdateServiceTest {
new WebViewProviderInfo(secondPackage, "", true, false, null)};
setupWithPackages(packages);
// Start with the setting pointing to the second package
- mTestSystemImpl.updateUserSetting(null, secondPackage);
+ mTestSystemImpl.updateUserSetting(secondPackage);
// Have all packages be enabled, so that we can change provider however we want to
setEnabledAndValidPackageInfos(packages);
@@ -572,7 +572,7 @@ public class WebViewUpdateServiceTest {
// Check that the boot time logic re-enables the fallback package.
runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).enablePackageForAllUsers(
- Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
+ Mockito.eq(testPackage), Mockito.eq(true));
// Fake the message about the enabling having changed the package state,
// and check we now use that package.
@@ -657,7 +657,7 @@ public class WebViewUpdateServiceTest {
null)};
setupWithPackages(packages);
// Start with the setting pointing to the secondary package
- mTestSystemImpl.updateUserSetting(null, secondaryPackage);
+ mTestSystemImpl.updateUserSetting(secondaryPackage);
int secondaryUserId = 10;
int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID;
if (multiUser) {
@@ -710,7 +710,7 @@ public class WebViewUpdateServiceTest {
null)};
setupWithPackages(packages);
// Start with the setting pointing to the secondary package
- mTestSystemImpl.updateUserSetting(null, secondaryPackage);
+ mTestSystemImpl.updateUserSetting(secondaryPackage);
setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
int newUser = 100;
mTestSystemImpl.addUser(newUser);
@@ -832,14 +832,13 @@ public class WebViewUpdateServiceTest {
true /* installed */));
// Set user-chosen package
- mTestSystemImpl.updateUserSetting(null, chosenPackage);
+ mTestSystemImpl.updateUserSetting(chosenPackage);
runWebViewBootPreparationOnMainSync();
// Verify that we switch the setting to point to the current package
- Mockito.verify(mTestSystemImpl).updateUserSetting(
- Mockito.anyObject(), Mockito.eq(nonChosenPackage));
- assertEquals(nonChosenPackage, mTestSystemImpl.getUserChosenWebViewProvider(null));
+ Mockito.verify(mTestSystemImpl).updateUserSetting(Mockito.eq(nonChosenPackage));
+ assertEquals(nonChosenPackage, mTestSystemImpl.getUserChosenWebViewProvider());
checkPreparationPhasesForPackage(nonChosenPackage, 1);
}
@@ -976,7 +975,7 @@ public class WebViewUpdateServiceTest {
setEnabledAndValidPackageInfos(packages);
// Start with the setting pointing to the third package
- mTestSystemImpl.updateUserSetting(null, thirdPackage);
+ mTestSystemImpl.updateUserSetting(thirdPackage);
runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(thirdPackage, 1);
@@ -1167,7 +1166,7 @@ public class WebViewUpdateServiceTest {
setupWithPackages(webviewPackages);
// Start with the setting pointing to the uninstalled package
- mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+ mTestSystemImpl.updateUserSetting(uninstalledPackage);
int secondaryUserId = 5;
if (multiUser) {
mTestSystemImpl.addUser(secondaryUserId);
@@ -1220,7 +1219,7 @@ public class WebViewUpdateServiceTest {
setupWithPackages(webviewPackages);
// Start with the setting pointing to the uninstalled package
- mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+ mTestSystemImpl.updateUserSetting(uninstalledPackage);
int secondaryUserId = 412;
mTestSystemImpl.addUser(secondaryUserId);
@@ -1277,7 +1276,7 @@ public class WebViewUpdateServiceTest {
setupWithPackages(webviewPackages);
// Start with the setting pointing to the uninstalled package
- mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+ mTestSystemImpl.updateUserSetting(uninstalledPackage);
int secondaryUserId = 4;
mTestSystemImpl.addUser(secondaryUserId);
@@ -1290,7 +1289,7 @@ public class WebViewUpdateServiceTest {
0 /* updateTime */, (testHidden ? true : false) /* hidden */));
// Start with the setting pointing to the uninstalled package
- mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
+ mTestSystemImpl.updateUserSetting(uninstalledPackage);
runWebViewBootPreparationOnMainSync();
@@ -1458,7 +1457,7 @@ public class WebViewUpdateServiceTest {
runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
- mTestSystemImpl.setMultiProcessSetting(null /* context */, settingValue);
+ mTestSystemImpl.setMultiProcessSetting(settingValue);
assertEquals(expectEnabled, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
}
@@ -1492,7 +1491,7 @@ public class WebViewUpdateServiceTest {
};
setupWithPackages(packages);
// Start with the setting pointing to the invalid package
- mTestSystemImpl.updateUserSetting(null, oldSdkPackage.packageName);
+ mTestSystemImpl.updateUserSetting(oldSdkPackage.packageName);
mTestSystemImpl.setPackageInfo(newSdkPackage);
mTestSystemImpl.setPackageInfo(currentSdkPackage);
@@ -1545,8 +1544,7 @@ public class WebViewUpdateServiceTest {
// Check that the boot time logic re-enables the default package.
runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl)
- .enablePackageForAllUsers(
- Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
+ .enablePackageForAllUsers(Mockito.eq(testPackage), Mockito.eq(true));
}
@Test
@@ -1570,8 +1568,7 @@ public class WebViewUpdateServiceTest {
// Check that the boot time logic tries to install the default package.
runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl)
- .installExistingPackageForAllUsers(
- Matchers.anyObject(), Mockito.eq(testPackage));
+ .installExistingPackageForAllUsers(Mockito.eq(testPackage));
}
@Test
@@ -1598,8 +1595,7 @@ public class WebViewUpdateServiceTest {
// Check that we try to re-install the default package.
Mockito.verify(mTestSystemImpl)
- .installExistingPackageForAllUsers(
- Matchers.anyObject(), Mockito.eq(testPackage));
+ .installExistingPackageForAllUsers(Mockito.eq(testPackage));
}
/**
@@ -1632,8 +1628,7 @@ public class WebViewUpdateServiceTest {
// Check that we try to re-install the default package for all users.
Mockito.verify(mTestSystemImpl)
- .installExistingPackageForAllUsers(
- Matchers.anyObject(), Mockito.eq(testPackage));
+ .installExistingPackageForAllUsers(Mockito.eq(testPackage));
}
private void testDefaultPackageChosen(PackageInfo packageInfo) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 4bbbc2b28dad..b07940ad8de3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -186,6 +186,9 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParserException;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -205,9 +208,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWith(ParameterizedAndroidJunit4.class)
@@ -5022,6 +5022,34 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule_forWatch() {
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule =
+ new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId =
+ mZenModeHelper.addAutomaticZenRule(
+ mPkg, rule, UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(
+ ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateWithDiff =
+ new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+ CONDITION_TRUE);
+ }
+
+ @Test
@EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
public void updateAutomaticZenRule_ruleDisabledByUser_doesNotReactivateOnReenable() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 9dac23f075e6..d7004e72bc52 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1746,10 +1746,6 @@ public class VibrationThreadTest {
assertTrue("Tested duration=" + duration4, duration4 < 2000);
// Effect5: played normally after effect4, which may or may not have played.
-
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5));
- verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED);
-
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
fakeVibrator.getEffectSegments(vibrationId5));
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ef944dbba3ca..5ae5677b9b53 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -47,6 +47,7 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
@@ -103,6 +104,7 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.pm.BackgroundUserSoundNotifier;
import org.junit.After;
import org.junit.Before;
@@ -809,6 +811,32 @@ public class VibratorManagerServiceTest {
}
@Test
+ @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
+ public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ var vib = vibrate(service,
+ VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), ALARM_ATTRS);
+
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+
+ service.mIntentReceiver.onReceive(mContextSpy, new Intent(
+ BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
+
+ assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+
+ var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(statsInfoCaptor.capture());
+
+ VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
+ assertEquals(Vibration.Status.CANCELLED_BY_FOREGROUND_USER.getProtoEnumValue(),
+ touchMetrics.status);
+ }
+
+ @Test
public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
VibratorManagerService service = createSystemReadyService();
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 88419e9c441b..1549b2de78c3 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
+<!-- Copyright 2024 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,6 +14,8 @@
limitations under the License.
-->
<bookmarks>
+ <!-- the key combinations for the following shortcuts must be in sync
+ with the key combinations sent by the test in ModifierShortcutTests.java -->
<bookmark
role="android.app.role.BROWSER"
shortcut="b" />
@@ -38,4 +40,37 @@
<bookmark
category="android.intent.category.APP_CALCULATOR"
shortcut="u" />
+
+ <!-- The following shortcuts will not be invoked by tests but are here to
+ provide test coverage of parsing the different types of shortcut. -->
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="a" />
+ <bookmark
+ package="com.test2"
+ class="com.test.BookmarkTest"
+ shortcut="d" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="a"
+ shift="true" />
+
+ <!-- It's intended that this package/class will NOT resolve so we test the resolution
+ failure case. -->
+ <bookmark
+ package="com.test3"
+ class="com.test.BookmarkTest"
+ shortcut="f" />
+
</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 896edffee875..1c331166d317 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -24,8 +24,8 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV
import android.view.ViewConfiguration;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 8c375d413950..5533ff909c50 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 2024 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.
@@ -19,15 +19,22 @@ package com.android.server.policy;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
+import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Handler;
@@ -58,27 +65,56 @@ public class ModifierShortcutManagerTests {
private Handler mHandler;
private Context mContext;
private Resources mResources;
+ private PackageManager mPackageManager;
@Before
public void setUp() {
mHandler = new Handler(Looper.getMainLooper());
mContext = spy(getInstrumentation().getTargetContext());
mResources = spy(mContext.getResources());
+ mPackageManager = spy(mContext.getPackageManager());
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
+ try {
+ // Keep packageName / className in sync with
+ // services/tests/wmtests/res/xml/bookmarks.xml
+ ActivityInfo testActivityInfo = new ActivityInfo();
+ testActivityInfo.applicationInfo = new ApplicationInfo();
+ testActivityInfo.packageName =
+ testActivityInfo.applicationInfo.packageName = "com.test";
+
+ doReturn(testActivityInfo).when(mPackageManager).getActivityInfo(
+ eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt());
+ doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager)
+ .getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")),
+ anyInt());
+ } catch (PackageManager.NameNotFoundException ignored) { }
+ doReturn(new String[] { "com.test" }).when(mPackageManager)
+ .canonicalToCurrentPackageNames(aryEq(new String[] { "com.test2" }));
+
mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
}
@Test
public void test_getApplicationLaunchKeyboardShortcuts() {
+ // Expected values here determined by the number of shortcuts defined in
+ // services/tests/wmtests/res/xml/bookmarks.xml
+
+ // Total valid shortcuts.
KeyboardShortcutGroup group =
mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1);
- assertEquals(8, group.getItems().size());
+ assertEquals(13, group.getItems().size());
+
+ // Total valid shift shortcuts.
+ assertEquals(3, group.getItems().stream()
+ .filter(s -> s.getModifiers() == (KeyEvent.META_SHIFT_ON | KeyEvent.META_META_ON))
+ .count());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index b4505fad1b20..24fc7ee0c392 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2955,7 +2955,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testStartingWindowInTaskFragment() {
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
final WindowState startingWindow = createWindowState(
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
activity1.addWindow(startingWindow);
@@ -3011,6 +3012,28 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
+ public void testStartingWindowInTaskFragmentWithVisibleTask() {
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity1.getTask();
+ final Rect taskBounds = task.getBounds();
+ final Rect tfBounds = new Rect(taskBounds.left, taskBounds.top,
+ taskBounds.left + taskBounds.width() / 2, taskBounds.bottom);
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).setParentTask(task)
+ .setBounds(tfBounds).build();
+
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
+ final WindowState startingWindow = createWindowState(
+ new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
+ taskFragment.addChild(activity2);
+ activity2.addWindow(startingWindow);
+ activity2.mStartingData = mock(StartingData.class);
+ activity2.attachStartingWindow(startingWindow);
+
+ assertNull(activity2.mStartingData.mAssociatedTask);
+ assertNull(task.mSharedStartingData);
+ }
+
+ @Test
public void testTransitionAnimationBounds() {
removeGlobalMinSizeRestriction();
final Task task = new TaskBuilder(mSupervisor)
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
new file mode 100644
index 000000000000..ddd6d562abb1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2024 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;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatAspectRatioOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatAspectRatioOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+ robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ false);
+
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+
+ robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ robot.checkShouldApplyUserFullscreenOverride(/* expected */ false);
+ });
+ }
+
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN);
+
+ robot.checkShouldApplyUserFullscreenOverride(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldEnableUserAspectRatioSettings(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testShouldEnableUserAspectRatioSettings_ignoreOrientation_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldEnableUserAspectRatioSettings(/* enabled */ false);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ false);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_ignoreOrientation_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false);
+ robot.activity().setIgnoreOrientationRequest(/* enabled */ true);
+ robot.activity().createActivityWithComponent();
+ robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+
+ robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testShouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
+ runTestScenario((robot)-> {
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatio(/* expected */ true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testShouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue() {
+ runTestScenario((robot)-> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatio(/* expected */ true);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testShouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testShouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+ });
+ }
+
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse() {
+ runTestScenario((robot)-> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatio(/* expected */ false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final AspectRatioOverridesRobotTest robot =
+ new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class AspectRatioOverridesRobotTest extends AppCompatRobotBase {
+
+ AspectRatioOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ void checkShouldApplyUserFullscreenOverride(boolean expected) {
+ assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+ .shouldApplyUserFullscreenOverride());
+ }
+
+ void checkShouldEnableUserAspectRatioSettings(boolean expected) {
+ assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings());
+ }
+
+ void checkShouldApplyUserMinAspectRatioOverride(boolean expected) {
+ assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride());
+ }
+
+ void checkShouldOverrideMinAspectRatio(boolean expected) {
+ assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
+ }
+
+ @NonNull
+ private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() {
+ return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides();
+ }
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index d8c7fb3594c1..de99f546ab07 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAME
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
@@ -36,6 +37,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Rule;
@@ -286,6 +288,88 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
});
}
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().activateCameraInPolicy(/* isCameraActive */ false);
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ robot.activity().createActivityWithComponent();
+ robot.activity().activateCameraInPolicy(/* isCameraActive */ true);
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -323,6 +407,11 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
.shouldApplyFreeformTreatmentForCameraCompat(), expected);
}
+ void checkShouldOverrideMinAspectRatioForCamera(boolean expected) {
+ Assert.assertEquals(getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera(), expected);
+ }
+
void checkIsCameraActive(boolean active) {
Assert.assertEquals(getAppCompatCameraOverrides().isCameraActive(), active);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java
index d568eecfd1c5..361177f480a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java
@@ -32,27 +32,27 @@ import androidx.annotation.NonNull;
*/
class AppCompatComponentPropRobot {
@NonNull
- private final WindowManagerService mWm;
+ private final PackageManager mPackageManager;
AppCompatComponentPropRobot(@NonNull WindowManagerService wm) {
- mWm = wm;
+ mPackageManager = wm.mContext.getPackageManager();
+ spyOn(mPackageManager);
}
void enable(@NonNull String propertyName) {
- setPropertyValue(propertyName, /* enabled */ true);
+ setPropertyValue(propertyName, "", "", /* enabled */ true);
}
void disable(@NonNull String propertyName) {
- setPropertyValue(propertyName, /* enabled */ false);
+ setPropertyValue(propertyName, "", "", /* enabled */ false);
}
- private void setPropertyValue(@NonNull String propertyName, boolean enabled) {
+ private void setPropertyValue(@NonNull String propertyName, @NonNull String packageName,
+ @NonNull String className, boolean enabled) {
final PackageManager.Property property = new PackageManager.Property(propertyName,
- /* value */ enabled, /* packageName */ "", /* className */ "");
- final PackageManager pm = mWm.mContext.getPackageManager();
- spyOn(pm);
+ /* value */ enabled, packageName, className);
try {
- doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
+ doReturn(property).when(mPackageManager).getProperty(eq(propertyName), anyString());
} catch (PackageManager.NameNotFoundException e) {
fail(e.getLocalizedMessage());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index cb3cf6bd2a5c..0a1b16bfc3e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -66,6 +66,4 @@ class AppCompatConfigurationRobot {
doReturn(enabled).when(mAppCompatConfiguration)
.isCameraCompatSplitScreenAspectRatioEnabled();
}
-
-
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 63c14b90958f..afa22bc5eae8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -67,6 +67,7 @@ import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.OnBackInvokedDispatcher;
+import android.window.TaskFragmentOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowOnBackInvokedDispatcher;
@@ -670,25 +671,29 @@ public class BackNavigationControllerTests extends WindowTestsBase {
}
@Test
- public void testAdjacentFocusInActivityEmbedding() {
+ public void testBackOnMostRecentWindowInActivityEmbedding() {
mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
final Task task = createTask(mDefaultDisplay);
- final TaskFragment primaryTf = createTaskFragmentWithActivity(task);
- final TaskFragment secondaryTf = createTaskFragmentWithActivity(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord primaryActivity = primaryTf.getTopMostActivity();
final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity();
primaryTf.setAdjacentTaskFragment(secondaryTf);
secondaryTf.setAdjacentTaskFragment(primaryTf);
- final WindowState windowState = mock(WindowState.class);
- windowState.mActivityRecord = primaryActivity;
- doReturn(windowState).when(mWm).getFocusedWindowLocked();
- doReturn(primaryTf).when(windowState).getTaskFragment();
+ final WindowState primaryWindow = mock(WindowState.class);
+ final WindowState secondaryWindow = mock(WindowState.class);
+ doReturn(primaryActivity).when(primaryWindow).getActivityRecord();
+ doReturn(secondaryActivity).when(secondaryWindow).getActivityRecord();
doReturn(1L).when(primaryActivity).getLastWindowCreateTime();
doReturn(2L).when(secondaryActivity).getLastWindowCreateTime();
+ doReturn(mDisplayContent).when(primaryActivity).getDisplayContent();
+ doReturn(secondaryWindow).when(mDisplayContent).findFocusedWindow(eq(secondaryActivity));
- startBackNavigation();
- verify(mWm).moveFocusToActivity(eq(secondaryActivity));
+ final WindowState mostRecentUsedWindow =
+ mWm.getMostRecentUsedEmbeddedWindowForBack(primaryWindow);
+ assertThat(mostRecentUsedWindow).isEqualTo(secondaryWindow);
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 23a88a1774f4..b687042edfc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,9 +21,19 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.wm.DesktopModeLaunchParamsModifier.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
+import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
+import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
+import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -59,6 +69,10 @@ import org.junit.runner.RunWith;
@RunWith(WindowTestRunner.class)
public class DesktopModeLaunchParamsModifierTests extends
LaunchParamsModifierTestsBase<DesktopModeLaunchParamsModifier> {
+ private static final Rect LANDSCAPE_DISPLAY_BOUNDS = new Rect(0, 0, 2560, 1600);
+ private static final Rect PORTRAIT_DISPLAY_BOUNDS = new Rect(0, 0, 1600, 2560);
+ private static final float LETTERBOX_ASPECT_RATIO = 1.3f;
+
@Before
public void setUp() throws Exception {
mActivity = new ActivityBuilder(mAtm).build();
@@ -158,6 +172,7 @@ public class DesktopModeLaunchParamsModifierTests extends
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() {
setupDesktopModeLaunchParamsModifier();
@@ -169,6 +184,209 @@ public class DesktopModeLaunchParamsModifierTests extends
(int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight =
(int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultLandscapeBounds_landscapeDevice_resizable_undefinedOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+
+ final int desiredWidth =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultLandscapeBounds_landscapeDevice_resizable_landscapeOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+ final int desiredWidth =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+ doReturn(LETTERBOX_ASPECT_RATIO).when(()
+ -> calculateAspectRatio(any(), any()));
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+ final int desiredWidth =
+ (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultLandscapeBounds_landscapeDevice_unResizable_landscapeOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+
+ final int desiredWidth =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ public void testUnResizablePortraitBounds_landscapeDevice_unResizable_portraitOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+ doReturn(LETTERBOX_ASPECT_RATIO).when(()
+ -> calculateAspectRatio(any(), any()));
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredWidth = (int) (desiredHeight / LETTERBOX_ASPECT_RATIO);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultPortraitBounds_portraitDevice_resizable_undefinedOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+
+ final int desiredWidth =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultPortraitBounds_portraitDevice_resizable_portraitOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+ final int desiredWidth =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+ doReturn(LETTERBOX_ASPECT_RATIO).when(()
+ -> calculateAspectRatio(any(), any()));
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+ final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
+ - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
+ final int desiredHeight = (int)
+ ((PORTRAIT_DISPLAY_BOUNDS.width() / LETTERBOX_ASPECT_RATIO) + 0.5f);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultPortraitBounds_portraitDevice_unResizable_portraitOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+
+ final int desiredWidth =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ public void testUnResizableLandscapeBounds_portraitDevice_unResizable_landscapeOrientation() {
+ setupDesktopModeLaunchParamsModifier();
+ doReturn(LETTERBOX_ASPECT_RATIO).when(()
+ -> calculateAspectRatio(any(), any()));
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+
+ final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
+ - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
+ final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO);
+
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(desiredWidth, mResult.mBounds.width());
assertEquals(desiredHeight, mResult.mBounds.height());
@@ -192,6 +410,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_CenterToDisplay() {
setupDesktopModeLaunchParamsModifier();
@@ -207,6 +426,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_LeftGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -222,6 +442,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_TopGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -237,6 +458,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_TopLeftGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -252,6 +474,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_RightGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -267,6 +490,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_BottomGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -282,6 +506,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_RightBottomGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -297,6 +522,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutFractionBounds() {
setupDesktopModeLaunchParamsModifier();
@@ -312,6 +538,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -327,6 +554,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -342,6 +570,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopLeftGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -359,6 +588,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_RightGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -374,6 +604,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -389,6 +620,7 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomRightGravity() {
setupDesktopModeLaunchParamsModifier();
@@ -422,6 +654,38 @@ public class DesktopModeLaunchParamsModifierTests extends
assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
}
+ private Task createTask(DisplayContent display, int orientation, Boolean isResizeable) {
+ final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE
+ : RESIZE_MODE_UNRESIZEABLE;
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ task.setResizeMode(resizeMode);
+ mActivity = new ActivityBuilder(task.mAtmService)
+ .setTask(task)
+ .setScreenOrientation(orientation)
+ .setOnTop(true).build();
+
+ mActivity.onDisplayChanged(display);
+ mActivity.setOccludesParent(true);
+ mActivity.setVisible(true);
+ mActivity.setVisibleRequested(true);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(/* ignoreOrientationRequest */ true);
+
+ return task;
+ }
+
+ private TestDisplayContent createDisplayContent(int orientation, Rect displayBounds) {
+ final TestDisplayContent display = new TestDisplayContent
+ .Builder(mAtm, displayBounds.width(), displayBounds.height())
+ .setPosition(DisplayContent.POSITION_TOP).build();
+ display.setBounds(displayBounds);
+ display.getConfiguration().densityDpi = DENSITY_DEFAULT;
+ display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
+ display.getDefaultTaskDisplayArea().setWindowingMode(orientation);
+
+ return display;
+ }
+
private void setupDesktopModeLaunchParamsModifier() {
setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ true,
/*enforceDeviceRestrictions=*/ true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index d318f0047c08..44c7057b2294 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,19 +19,12 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -119,8 +112,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
mController = new LetterboxUiController(mWm, mActivity);
}
-
-
@Test
public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
@@ -320,164 +311,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
return mainWindow;
}
- // shouldApplyUser...Override
- @Test
- public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
- /* value */ true);
-
- doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled();
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserFullscreenOverride());
- }
-
- @Test
- public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
- throws Exception {
- prepareActivityThatShouldApplyUserFullscreenOverride();
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
- /* value */ false);
-
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserFullscreenOverride());
- }
-
- @Test
- public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
- throws Exception {
- prepareActivityThatShouldApplyUserFullscreenOverride();
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserFullscreenOverride());
- }
-
- @Test
- public void testShouldApplyUserFullscreenOverride_returnsTrue() {
- prepareActivityThatShouldApplyUserFullscreenOverride();
-
- assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserFullscreenOverride());
- }
-
- @Test
- public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse()
- throws Exception {
- prepareActivityThatShouldApplyUserMinAspectRatioOverride();
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings());
- }
-
- @Test
- public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue()
- throws Exception {
-
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mActivity = setUpActivityWithComponent();
- prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings());
- }
-
- @Test
- public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientation_returnsFalse()
- throws Exception {
- prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings());
- }
-
- @Test
- public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
- throws Exception {
- prepareActivityThatShouldApplyUserMinAspectRatioOverride();
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings());
- }
-
- @Test
- public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
- throws Exception {
- doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled();
- mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
-
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride());
- }
-
- @Test
- public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
- prepareActivityThatShouldApplyUserMinAspectRatioOverride();
- mDisplayContent.setIgnoreOrientationRequest(false);
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride());
- }
-
- @Test
- public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
- prepareActivityThatShouldApplyUserMinAspectRatioOverride();
-
- assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride());
- }
-
- @Test
- public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientation_returnsFalse() {
- prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride());
- }
-
- private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
- boolean orientationRequest) {
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
- doReturn(orientationRequest).when(
- mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled();
- mDisplayContent.setIgnoreOrientationRequest(true);
- doReturn(USER_MIN_ASPECT_RATIO_3_2)
- .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
- .getUserMinAspectRatioOverrideCode();
- }
-
- private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
- prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true);
- }
-
- private void prepareActivityThatShouldApplyUserFullscreenOverride() {
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
- doReturn(true).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled();
- mDisplayContent.setIgnoreOrientationRequest(true);
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
- .getUserMinAspectRatioOverrideCode();
- }
-
// shouldUseDisplayLandscapeNaturalOrientation
@Test
@@ -595,156 +428,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
}
@Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
- public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
- mActivity = setUpActivityWithComponent();
-
- assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
- public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mActivity = setUpActivityWithComponent();
-
- assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
- public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
- public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
- public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
- public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
- mActivity = setUpActivityWithComponent();
- doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).isCameraActive();
-
- assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mActivity = setUpActivityWithComponent();
- doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).isCameraActive();
-
- assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mActivity = setUpActivityWithComponent();
- doReturn(false).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).isCameraActive();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mActivity = setUpActivityWithComponent();
- doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).isCameraActive();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
- mActivity = setUpActivityWithComponent();
- doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).isCameraActive();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
- mActivity = setUpActivityWithComponent();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
-
- doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).isCameraActive();
-
- assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera());
- }
-
- @Test
@EnableCompatChanges({FORCE_RESIZE_APP})
public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
mController = new LetterboxUiController(mWm, mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 0bf27d11493b..f93ffb83178f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -747,6 +747,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
}
+ @android.platform.test.annotations.RequiresFlagsDisabled(
+ com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testLaunchRemoteAnimationWithoutImeBehind() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
index e3f8e8c54b73..8abf3f86c01e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -36,8 +36,8 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.server.wm.utils.CommonUtils;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a816aa9b7598..d5d284783978 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -203,6 +203,7 @@ public class SystemServicesTestRule implements TestRule {
.mockStatic(LockGuard.class, mockStubOnly)
.mockStatic(Watchdog.class, mockStubOnly)
.spyStatic(DesktopModeHelper.class)
+ .spyStatic(DesktopModeBoundsCalculator.class)
.strictness(Strictness.LENIENT)
.startMocking();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
deleted file mode 100644
index d5356774ffdb..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT;
-import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
-import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.util.DisplayMetrics;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link TaskPositioner} class.
- *
- * Build/Install/Run:
- * atest WmTests:TaskPositionerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskPositionerTests extends WindowTestsBase {
-
- private static final boolean DEBUGGING = false;
- private static final String TAG = "TaskPositionerTest";
-
- private static final int MOUSE_DELTA_X = 5;
- private static final int MOUSE_DELTA_Y = 5;
-
- private int mMinVisibleWidth;
- private int mMinVisibleHeight;
- private TaskPositioner mPositioner;
-
- @Before
- public void setUp() {
- TaskPositioner.setFactory(null);
-
- final DisplayMetrics dm = mDisplayContent.getDisplayMetrics();
-
- // This should be the same calculation as the TaskPositioner uses.
- mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
- mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
- removeGlobalMinSizeRestriction();
-
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true)
- .build();
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "window");
- mPositioner = new TaskPositioner(mWm);
- mPositioner.register(mDisplayContent, win);
-
- win.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
- }
-
- @After
- public void tearDown() {
- TaskPositioner.setFactory(null);
- }
-
- @Test
- public void testOverrideFactory() {
- final boolean[] created = new boolean[1];
- created[0] = false;
- TaskPositioner.setFactory(new TaskPositioner.Factory() {
- @Override
- public TaskPositioner create(WindowManagerService service) {
- created[0] = true;
- return null;
- }
- });
-
- assertNull(TaskPositioner.create(mWm));
- assertTrue(created[0]);
- }
-
- /** This tests that the window can move in all directions. */
- @Test
- public void testMoveWindow() {
- final Rect displayBounds = mDisplayContent.getBounds();
- final int windowSize = Math.min(displayBounds.width(), displayBounds.height()) / 2;
- final int left = displayBounds.centerX() - windowSize / 2;
- final int top = displayBounds.centerY() - windowSize / 2;
- final Rect r = new Rect(left, top, left + windowSize, top + windowSize);
- mPositioner.mTask.setBounds(r);
- mPositioner.startDrag(false /* resizing */, false /* preserveOrientation */, left, top);
-
- // Move upper left.
- mPositioner.notifyMoveLocked(left - MOUSE_DELTA_X, top - MOUSE_DELTA_Y);
- r.offset(-MOUSE_DELTA_X, -MOUSE_DELTA_Y);
- assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
- // Move bottom right.
- mPositioner.notifyMoveLocked(left, top);
- r.offset(MOUSE_DELTA_X, MOUSE_DELTA_Y);
- assertBoundsEquals(r, mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that free resizing will allow to change the orientation as well
- * as does some basic tests (e.g. dragging in Y only will keep X stable).
- */
- @Test
- public void testBasicFreeWindowResizing() {
- final Rect r = new Rect(100, 220, 700, 520);
- final int midY = (r.top + r.bottom) / 2;
- mPositioner.mTask.setBounds(r, true);
-
- // Start a drag resize starting upper left.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
- assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
- // Drag to a good landscape size.
- mPositioner.resizeDrag(0.0f, 0.0f);
- assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a good portrait size.
- mPositioner.resizeDrag(400.0f, 0.0f);
- assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a too small size for the width.
- mPositioner.resizeDrag(2000.0f, r.top);
- assertBoundsEquals(
- new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a too small size for the height.
- mPositioner.resizeDrag(r.left, 2000.0f);
- assertBoundsEquals(
- new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Start a drag resize left and see that only the left coord changes..
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, midY);
-
- // Drag to the left.
- mPositioner.resizeDrag(0.0f, midY);
- assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the right.
- mPositioner.resizeDrag(200.0f, midY);
- assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the top
- mPositioner.resizeDrag(r.left, 0.0f);
- assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the bottom
- mPositioner.resizeDrag(r.left, 1000.0f);
- assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored.
- */
- @Test
- public void testFreeWindowResizingTestAllEdges() {
- final Rect r = new Rect(100, 220, 700, 520);
- final int midX = (r.left + r.right) / 2;
- final int midY = (r.top + r.bottom) / 2;
- mPositioner.mTask.setBounds(r, true);
-
- // Drag upper left.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
- mPositioner.resizeDrag(0.0f, 0.0f);
- assertNotEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertNotEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag upper.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX,
- r.top - MOUSE_DELTA_Y);
- mPositioner.resizeDrag(0.0f, 0.0f);
- assertEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertNotEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag upper right.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
- mPositioner.resizeDrag(r.right + 100, 0.0f);
- assertEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertNotEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertNotEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag right.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.right + MOUSE_DELTA_X, midY);
- mPositioner.resizeDrag(r.right + 100, 0.0f);
- assertEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertNotEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag bottom right.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y);
- mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
- assertEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertNotEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag bottom.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX,
- r.bottom + MOUSE_DELTA_Y);
- mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
- assertEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag bottom left.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y);
- mPositioner.resizeDrag(0.0f, r.bottom + 100);
- assertNotEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
-
- // Drag left.
- mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, midY);
- mPositioner.resizeDrag(0.0f, r.bottom + 100);
- assertNotEquals(r.left, mPositioner.getWindowDragBounds().left);
- assertEquals(r.right, mPositioner.getWindowDragBounds().right);
- assertEquals(r.top, mPositioner.getWindowDragBounds().top);
- assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
- }
-
- /**
- * This tests that a constrained landscape window will keep the aspect and do the
- * right things upon resizing when dragged from the top left corner.
- */
- @Test
- public void testLandscapePreservedWindowResizingDragTopLeft() {
- final Rect r = new Rect(100, 220, 700, 520);
- mPositioner.mTask.setBounds(r, true);
-
- mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
- assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
- // Drag to a good landscape size.
- mPositioner.resizeDrag(0.0f, 0.0f);
- assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a good portrait size.
- mPositioner.resizeDrag(400.0f, 0.0f);
- int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT);
- assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a too small size for the width.
- mPositioner.resizeDrag(2000.0f, r.top);
- final int w = mMinVisibleWidth;
- final int h = Math.round(w / MIN_ASPECT);
- assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a too small size for the height.
- mPositioner.resizeDrag(r.left, 2000.0f);
- assertBoundsEquals(
- new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that a constrained landscape window will keep the aspect and do the
- * right things upon resizing when dragged from the left corner.
- */
- @Test
- public void testLandscapePreservedWindowResizingDragLeft() {
- final Rect r = new Rect(100, 220, 700, 520);
- final int midY = (r.top + r.bottom) / 2;
- mPositioner.mTask.setBounds(r, true);
-
- mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, midY);
-
- // Drag to the left.
- mPositioner.resizeDrag(0.0f, midY);
- assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the right.
- mPositioner.resizeDrag(200.0f, midY);
- assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag all the way to the right and see the height also shrinking.
- mPositioner.resizeDrag(2000.0f, midY);
- final int w = mMinVisibleWidth;
- final int h = Math.round((float) w / MIN_ASPECT);
- assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
- mPositioner.getWindowDragBounds());
-
- // Drag to the top.
- mPositioner.resizeDrag(r.left, 0.0f);
- assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the bottom.
- mPositioner.resizeDrag(r.left, 1000.0f);
- assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that a constrained landscape window will keep the aspect and do the
- * right things upon resizing when dragged from the top corner.
- */
- @Test
- public void testLandscapePreservedWindowResizingDragTop() {
- final Rect r = new Rect(100, 220, 700, 520);
- final int midX = (r.left + r.right) / 2;
- mPositioner.mTask.setBounds(r, true);
-
- mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/, midX,
- r.top - MOUSE_DELTA_Y);
-
- // Drag to the left (no change).
- mPositioner.resizeDrag(0.0f, r.top);
- assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the right (no change).
- mPositioner.resizeDrag(2000.0f, r.top);
- assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the top.
- mPositioner.resizeDrag(300.0f, 0.0f);
- int h = r.bottom - MOUSE_DELTA_Y;
- int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT));
- assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the bottom.
- mPositioner.resizeDrag(r.left, 1000.0f);
- h = mMinVisibleHeight;
- assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that a constrained portrait window will keep the aspect and do the
- * right things upon resizing when dragged from the top left corner.
- */
- @Test
- public void testPortraitPreservedWindowResizingDragTopLeft() {
- final Rect r = new Rect(330, 100, 630, 600);
- mPositioner.mTask.setBounds(r, true);
-
- mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/,
- r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y);
- assertBoundsEquals(r, mPositioner.getWindowDragBounds());
-
- // Drag to a good landscape size.
- mPositioner.resizeDrag(0.0f, 0.0f);
- int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT);
- assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a good portrait size.
- mPositioner.resizeDrag(400.0f, 0.0f);
- assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to a too small size for the height and the the width shrinking.
- mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f);
- final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT));
- final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT));
- assertBoundsEquals(
- new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that a constrained portrait window will keep the aspect and do the
- * right things upon resizing when dragged from the left corner.
- */
- @Test
- public void testPortraitPreservedWindowResizingDragLeft() {
- final Rect r = new Rect(330, 100, 630, 600);
- final int midY = (r.top + r.bottom) / 2;
- mPositioner.mTask.setBounds(r, true);
-
- mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */,
- r.left - MOUSE_DELTA_X, midY);
-
- // Drag to the left.
- mPositioner.resizeDrag(0.0f, midY);
- int w = r.right - MOUSE_DELTA_X;
- int h = Math.round(w * MIN_ASPECT);
- assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h),
- mPositioner.getWindowDragBounds());
-
- // Drag to the right.
- mPositioner.resizeDrag(450.0f, midY);
- assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag all the way to the right.
- mPositioner.resizeDrag(2000.0f, midY);
- w = mMinVisibleWidth;
- h = Math.max(Math.round((float) w * MIN_ASPECT), r.height());
- assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
- mPositioner.getWindowDragBounds());
-
- // Drag to the top.
- mPositioner.resizeDrag(r.left, 0.0f);
- assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the bottom.
- mPositioner.resizeDrag(r.left, 1000.0f);
- assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- /**
- * This tests that a constrained portrait window will keep the aspect and do the
- * right things upon resizing when dragged from the top corner.
- */
- @Test
- public void testPortraitPreservedWindowResizingDragTop() {
- final Rect r = new Rect(330, 100, 630, 600);
- final int midX = (r.left + r.right) / 2;
- mPositioner.mTask.setBounds(r, true);
-
- mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, midX,
- r.top - MOUSE_DELTA_Y);
-
- // Drag to the left (no change).
- mPositioner.resizeDrag(0.0f, r.top);
- assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the right (no change).
- mPositioner.resizeDrag(2000.0f, r.top);
- assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the top.
- mPositioner.resizeDrag(300.0f, 0.0f);
- int h = r.bottom - MOUSE_DELTA_Y;
- int w = Math.min(r.width(), Math.round(h / MIN_ASPECT));
- assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
- mPositioner.getWindowDragBounds());
-
- // Drag to the bottom.
- mPositioner.resizeDrag(r.left, 1000.0f);
- h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT));
- w = Math.round(h / MIN_ASPECT);
- assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom),
- mPositioner.getWindowDragBounds());
- }
-
- private static void assertBoundsEquals(Rect expected, Rect actual) {
- if (DEBUGGING) {
- if (!expected.equals(actual)) {
- Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString()
- + ") " + Log.getStackTraceString(new Throwable()));
- }
- }
- assertEquals(expected, actual);
- }
-
- @Test
- public void testFinishingMovingWhenBinderDied() {
- spyOn(mWm.mTaskPositioningController);
-
- mPositioner.startDrag(false, false, 0 /* startX */, 0 /* startY */);
- verify(mWm.mTaskPositioningController, never()).finishTaskPositioning();
- mPositioner.binderDied();
- verify(mWm.mTaskPositioningController).finishTaskPositioning();
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
deleted file mode 100644
index bfc13d3d2ef2..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.InputChannel;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link TaskPositioningController} class.
- *
- * Build/Install/Run:
- * atest WmTests:TaskPositioningControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskPositioningControllerTests extends WindowTestsBase {
- private static final int TIMEOUT_MS = 1000;
-
- private TaskPositioningController mTarget;
- private WindowState mWindow;
-
- @Before
- public void setUp() throws Exception {
- assertNotNull(mWm.mTaskPositioningController);
- mTarget = mWm.mTaskPositioningController;
-
- when(mWm.mInputManager.transferTouchGesture(any(), any())).thenReturn(true);
-
- mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
- mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE);
- mWindow.mInputChannel = new InputChannel();
- mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
- doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor();
- }
-
- @FlakyTest(bugId = 291067614)
- @Test
- public void testStartAndFinishPositioning() {
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
-
- assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0));
-
- assertTrue(mTarget.isPositioningLocked());
- assertNotNull(mTarget.getDragWindowHandleLocked());
-
- mTarget.finishTaskPositioning();
- // Wait until the looper processes finishTaskPositioning.
- assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
-
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
- }
-
- @Test
- public void testFinishPositioningWhenAppRequested() {
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
-
- assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0));
-
- assertTrue(mTarget.isPositioningLocked());
- assertNotNull(mTarget.getDragWindowHandleLocked());
-
- mTarget.finishTaskPositioning(mWindow.mClient);
- // Wait until the looper processes finishTaskPositioning.
- assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
-
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
- }
-
- @Test
- public void testHandleTapOutsideTask() {
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
-
- final DisplayContent content = mock(DisplayContent.class);
- doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt());
- assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
-
- mTarget.handleTapOutsideTask(content, 0, 0);
- // Wait until the looper processes handleTapOutsideTask.
- assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
-
- assertTrue(mTarget.isPositioningLocked());
- assertNotNull(mTarget.getDragWindowHandleLocked());
-
- mTarget.finishTaskPositioning();
- // Wait until the looper processes finishTaskPositioning.
- assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
-
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
- }
-
- @Test
- public void testHandleTapOutsideNonResizableTask() {
- assertFalse(mTarget.isPositioningLocked());
- assertNull(mTarget.getDragWindowHandleLocked());
-
- final DisplayContent content = mock(DisplayContent.class);
- doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt());
- assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
-
- mWindow.getTask().setResizeMode(RESIZE_MODE_UNRESIZEABLE);
-
- mTarget.handleTapOutsideTask(content, 0, 0);
- // Wait until the looper processes handleTapOutsideTask.
- assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
-
- assertFalse(mTarget.isPositioningLocked());
- }
-
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 7d01b79fe866..720457e24370 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1573,7 +1573,8 @@ public class TransitionTests extends WindowTestsBase {
enteringAnimReports.clear();
doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean());
final boolean[] wasInFinishingTransition = { false };
- controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
+ controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener(
+ mDisplayContent.mDisplayId) {
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forToken(token);
@@ -1582,6 +1583,14 @@ public class TransitionTests extends WindowTestsBase {
}
}
});
+ final boolean[] calledListenerOnOtherDisplay = { false };
+ controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener(
+ mDisplayContent.mDisplayId + 1234) {
+ @Override
+ public void onAppTransitionFinishedLocked(IBinder token) {
+ calledListenerOnOtherDisplay[0] = true;
+ }
+ });
assertTrue(activity1.isVisible());
doReturn(false).when(task1).isTranslucent(null);
doReturn(false).when(task1).isTranslucentForTransition();
@@ -1592,6 +1601,7 @@ public class TransitionTests extends WindowTestsBase {
controller.finishTransition(closeTransition);
assertTrue(wasInFinishingTransition[0]);
+ assertFalse(calledListenerOnOtherDisplay[0]);
assertNull(controller.mFinishingTransition);
assertTrue(activity2.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 593e983f3d23..22def515a98e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import android.platform.test.annotations.Presubmit;
@@ -60,4 +61,24 @@ public class WindowContainerTraversalTests extends WindowTestsBase {
verify(c).accept(eq(mDockedDividerWindow));
verify(c).accept(eq(mImeWindow));
}
+
+ @android.platform.test.annotations.RequiresFlagsEnabled(
+ com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
+ @Test
+ public void testTraverseImeRegardlessOfImeTarget() {
+ mDisplayContent.setImeLayeringTarget(mAppWindow);
+ mDisplayContent.setImeInputTarget(mAppWindow);
+ mAppWindow.mHasSurface = false;
+ mAppWindow.mActivityRecord.setVisibleRequested(false);
+ mAppWindow.mActivityRecord.setVisible(false);
+
+ final boolean[] foundIme = { false };
+ mDisplayContent.forAllWindows(w -> {
+ if (w == mImeWindow) {
+ foundIme[0] = true;
+ }
+ }, true /* traverseTopToBottom */);
+ assertTrue("IME must be found", foundIme[0]);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e6648dad4bbe..0cb22ad47355 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -86,6 +87,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
ApplicationInfo info = mock(ApplicationInfo.class);
info.packageName = "test.package.name";
+ doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
mWpc = new WindowProcessController(
mAtm, info, null, 0, -1, null, mMockListener);
mWpc.setThread(mock(IApplicationThread.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
index e5f2f89ccead..eda78cb40c5d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
@@ -63,9 +63,6 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase {
resetCache();
}
- private static final String SYSTEM_PROPERTY_OVERRIDE_KEY =
- "sys.wmshell.desktopmode.dev_toggle_override";
-
@Test
@DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@@ -190,110 +187,6 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isEnabled_noProperty_overrideOn_featureFlagOff_returnsTrueAndPropertyOn() {
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
- setOverride(OVERRIDE_ON.getSetting());
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
- // Store System Property if not present
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting()));
- }
-
- @Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isEnabled_noProperty_overrideUnset_featureFlagOn_returnsTrueAndPropertyUnset() {
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
- setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
- // Store System Property if not present
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(
- DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isEnabled_noProperty_overrideUnset_featureFlagOff_returnsFalseAndPropertyUnset() {
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
- setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
- // Store System Property if not present
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(
- DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
- }
-
- @Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isEnabled_propertyNotInt_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc");
- setOverride(OVERRIDE_OFF.getSetting());
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
- // Store System Property if currently invalid
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting()));
- }
-
- @Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isEnabled_propertyInvalid_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2");
- setOverride(OVERRIDE_OFF.getSetting());
-
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
- // Store System Property if currently invalid
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting()));
- }
-
- @Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isEnabled_propertyOff_overrideOn_featureFlagOn_returnsFalseAndnoPropertyUpdate() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(
- OVERRIDE_OFF.getSetting()));
- setOverride(OVERRIDE_ON.getSetting());
-
- // Have a consistent override until reboot
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting()));
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isEnabled_propertyOn_overrideOff_featureFlagOff_returnsTrueAndnoPropertyUpdate() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(OVERRIDE_ON.getSetting()));
- setOverride(OVERRIDE_OFF.getSetting());
-
- // Have a consistent override until reboot
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting()));
- }
-
- @Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isEnabled_propertyUnset_overrideOff_featureFlagOn_returnsTrueAndnoPropertyUpdate() {
- System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY,
- String.valueOf(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
- setOverride(OVERRIDE_OFF.getSetting());
-
- // Have a consistent override until reboot
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
- assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
- .isEqualTo(String.valueOf(
- DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()));
- }
-
- @Test
@EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY})
public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
@@ -452,8 +345,5 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase {
"sCachedToggleOverride");
cachedToggleOverride.setAccessible(true);
cachedToggleOverride.set(null, null);
-
- // Clear override cache stored in System property
- System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 175a09db54e3..14044135eca7 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -16,6 +16,8 @@
package com.android.server.usb;
+import com.android.internal.annotations.Keep;
+
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY;
@@ -82,6 +84,7 @@ import android.util.Pair;
import android.util.Slog;
import android.text.TextUtils;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -147,6 +150,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
"DEVPATH=/devices/virtual/android_usb/android0";
private static final String ACCESSORY_START_MATCH =
"DEVPATH=/devices/virtual/misc/usb_accessory";
+ private static final String UDC_SUBSYS_MATCH =
+ "SUBSYSTEM=udc";
private static final String FUNCTIONS_PATH =
"/sys/class/android_usb/android0/functions";
private static final String STATE_PATH =
@@ -226,6 +231,9 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
private static UsbGadgetHal mUsbGadgetHal;
+ private final boolean mEnableUdcSysfsUsbStateUpdate;
+ private String mUdcName = "";
+
/**
* Counter for tracking UsbOperation operations.
*/
@@ -260,12 +268,9 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
if (DEBUG) Slog.d(TAG, "sEventLogger == null");
}
- String state = event.get("USB_STATE");
String accessory = event.get("ACCESSORY");
- if (state != null) {
- mHandler.updateState(state);
- } else if ("GETPROTOCOL".equals(accessory)) {
+ if ("GETPROTOCOL".equals(accessory)) {
if (DEBUG) Slog.d(TAG, "got accessory get protocol");
mHandler.setAccessoryUEventTime(SystemClock.elapsedRealtime());
resetAccessoryHandshakeTimeoutHandler();
@@ -279,6 +284,24 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
mHandler.setStartAccessoryTrue();
startAccessoryMode();
}
+
+ if (mEnableUdcSysfsUsbStateUpdate) {
+ if (!mUdcName.isEmpty()
+ && "udc".equals(event.get("SUBSYSTEM"))
+ && event.get("DEVPATH").contains(mUdcName)) {
+ String action = event.get("ACTION");
+ if ("add".equals(action)) {
+ nativeStartGadgetMonitor(mUdcName);
+ } else if ("remove".equals(action)) {
+ nativeStopGadgetMonitor();
+ }
+ }
+ } else {
+ String state = event.get("USB_STATE");
+ if (state != null) {
+ mHandler.updateState(state);
+ }
+ }
}
}
@@ -406,9 +429,28 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
// Watch for USB configuration changes
mUEventObserver = new UsbUEventObserver();
- mUEventObserver.startObserving(USB_STATE_MATCH);
mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+ mEnableUdcSysfsUsbStateUpdate =
+ android.hardware.usb.flags.Flags.enableUdcSysfsUsbStateUpdate()
+ && context.getResources().getBoolean(R.bool.config_enableUdcSysfsUsbStateUpdate);
+
+ if (mEnableUdcSysfsUsbStateUpdate) {
+ mUEventObserver.startObserving(UDC_SUBSYS_MATCH);
+ new Thread("GetUsbControllerSysprop") {
+ public void run() {
+ String udcName;
+ // blocking wait until usb controller sysprop is available
+ udcName = nativeWaitAndGetProperty(USB_CONTROLLER_NAME_PROPERTY);
+ nativeStartGadgetMonitor(udcName);
+ mUdcName = udcName;
+ Slog.v(TAG, "USB controller name " + udcName);
+ }
+ }.start();
+ } else {
+ mUEventObserver.startObserving(USB_STATE_MATCH);
+ }
+
sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
}
@@ -2609,11 +2651,27 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
dump.end(token);
}
+ /**
+ * Update usb state (Called by native code).
+ */
+ @Keep
+ private void updateGadgetState(String state) {
+ Slog.d(TAG, "Usb state update " + state);
+
+ mHandler.updateState(state);
+ }
+
private native String[] nativeGetAccessoryStrings();
private native ParcelFileDescriptor nativeOpenAccessory();
+ private native String nativeWaitAndGetProperty(String propName);
+
private native FileDescriptor nativeOpenControl(String usbFunction);
private native boolean nativeIsStartRequested();
+
+ private native boolean nativeStartGadgetMonitor(String udcName);
+
+ private native void nativeStopGadgetMonitor();
}
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 7607c64150d8..92af034217a9 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -15,4 +15,4 @@ per-file CarrierConfigManager.java=set noparent
per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
#Domain Selection is jointly owned, add additional owners for domain selection specific files
-per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
+per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,jaesikkong@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
index 2dc8ffba6e96..460de8c8113d 100644
--- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
@@ -25,4 +25,7 @@ parcelable ProvisionSubscriberId {
/** carrier id */
int mCarrierId;
+
+ /** apn */
+ String mNiddApn;
}
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index 08430f2f2744..4143f595f9a0 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@ public class BatteryUsageStatsPerfTest {
private static BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index b6672a0e2f4b..fad94d45c85d 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -698,7 +698,7 @@ public class PerfettoProtoLogImplTest {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
- "My test null string: %s", null);
+ "My test null string: %s", (Object) null);
} finally {
traceMonitor.stop(mWriter);
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index be9fb1b309f6..9a062e3b2f80 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -67,7 +67,7 @@ public class ProtologDataSourceTest {
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds = new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {});
+ final ProtoLogDataSource ds = new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
@@ -154,7 +154,7 @@ public class ProtologDataSourceTest {
private ProtoLogDataSource.TlsState createTlsState(
DataSourceConfigOuterClass.DataSourceConfig config) {
final ProtoLogDataSource ds =
- Mockito.spy(new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {}));
+ Mockito.spy(new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}));
ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
final ProtoLogDataSource.Instance dsInstance = Mockito.spy(