summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING6
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/ActivityThread.java5
-rw-r--r--core/java/android/app/AppOpsManager.java39
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/app/LoadedApk.java4
-rw-r--r--core/java/android/app/Notification.java62
-rw-r--r--core/java/android/app/Service.java19
-rw-r--r--core/java/android/app/activity_manager.aconfig10
-rw-r--r--core/java/android/content/pm/ActivityInfo.java11
-rw-r--r--core/java/android/hardware/biometrics/BiometricTestSession.java1
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java265
-rw-r--r--core/java/android/hardware/camera2/CameraExtensionCharacteristics.java24
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java63
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java9
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionUtils.java33
-rw-r--r--core/java/android/permission/flags.aconfig8
-rw-r--r--core/java/android/provider/Settings.java18
-rw-r--r--core/java/android/service/dreams/DreamService.java26
-rw-r--r--core/java/android/text/Layout.java122
-rw-r--r--core/java/android/view/View.java47
-rw-r--r--core/java/android/view/ViewRootImpl.java6
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig7
-rw-r--r--core/java/com/android/internal/os/PowerStats.java9
-rw-r--r--core/java/com/android/internal/util/ScreenshotHelper.java9
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/styles.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java32
-rw-r--r--core/tests/coretests/src/android/text/LayoutTest.java200
-rw-r--r--core/tests/coretests/src/android/view/ViewFrameRateTest.java24
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java22
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java14
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java86
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java171
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java275
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt68
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java110
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java96
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h46
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java107
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt48
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt140
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt)45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt116
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml3
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml3
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml3
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml1
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_0.xml34
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_1.xml32
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_2.xml29
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_not_connected.xml27
-rw-r--r--packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml2
-rw-r--r--packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml2
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml9
-rw-r--r--packages/SystemUI/res/values/strings.xml11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt406
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt181
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java119
-rw-r--r--packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt6
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt1
-rw-r--r--ravenwood/TEST_MAPPING20
-rwxr-xr-xravenwood/scripts/ravenwood-stats-collector.sh15
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java18
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java115
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java28
-rw-r--r--services/core/java/com/android/server/audio/AudioManagerShellCommand.java32
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java16
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java12
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig11
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java38
-rw-r--r--services/core/java/com/android/server/dreams/Android.bp11
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java24
-rw-r--r--services/core/java/com/android/server/dreams/flags.aconfig12
-rw-r--r--services/core/java/com/android/server/input/InputShellCommand.java48
-rw-r--r--services/core/java/com/android/server/net/TEST_MAPPING2
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java6
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java12
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java2
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java21
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java8
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java23
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java15
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java83
-rw-r--r--services/core/java/com/android/server/pm/ShortcutLauncher.java24
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java58
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackageItem.java22
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java212
-rw-r--r--services/core/java/com/android/server/policy/SideFpsEventHandler.java14
-rw-r--r--services/core/java/com/android/server/policy/SideFpsToast.java26
-rw-r--r--services/core/java/com/android/server/utils/AnrTimer.java65
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java14
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java25
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java21
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java24
-rw-r--r--services/core/jni/com_android_server_utils_AnrTimer.cpp100
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java22
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt122
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java157
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt4
-rw-r--r--services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java71
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java67
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java2
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java14
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java42
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java37
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java108
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java1
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java2
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt44
-rw-r--r--tests/Input/src/com/android/server/input/InputShellCommandTest.java8
-rw-r--r--tools/hoststubgen/TEST_MAPPING52
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt7
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt45
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt38
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt202
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt9
243 files changed, 5874 insertions, 1979 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index e649485ed5e5..e82df1203137 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -41,10 +41,10 @@
]
},
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
]
},
{
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f10c0fc21455..eabe1f1c271a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -872,6 +872,7 @@ package android.app {
public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getAttributionTag();
+ method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @Nullable public String getDeviceId();
method @Nullable public String getPackageName();
method @IntRange(from=0) public int getUid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 68cc17b6d92b..caaaf519eaca 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7438,6 +7438,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
mDdmSyncStageUpdater.next(Stage.Running);
+ long timestampApplicationOnCreateNs = 0;
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7480,8 +7481,10 @@ public final class ActivityThread extends ClientTransactionHandler
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
+ timestampApplicationOnCreateNs = SystemClock.elapsedRealtimeNanos();
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
+ timestampApplicationOnCreateNs = 0;
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
@@ -7519,7 +7522,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
try {
- mgr.finishAttachApplication(mStartSeq);
+ mgr.finishAttachApplication(mStartSeq, timestampApplicationOnCreateNs);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 20b2357e967d..2d0f6fccb8f2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3457,6 +3457,8 @@ public class AppOpsManager {
private @Nullable String mPackageName;
/** Attribution tag of the proxy that noted the op */
private @Nullable String mAttributionTag;
+ /** Persistent device Id of the proxy that noted the op */
+ private @Nullable String mDeviceId;
/**
* Reinit existing object with new state.
@@ -3464,14 +3466,16 @@ public class AppOpsManager {
* @param uid UID of the proxy app that noted the op
* @param packageName Package of the proxy that noted the op
* @param attributionTag attribution tag of the proxy that noted the op
+ * @param deviceId Persistent device Id of the proxy that noted the op
*
* @hide
*/
public void reinit(@IntRange(from = 0) int uid, @Nullable String packageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag, @Nullable String deviceId) {
mUid = Preconditions.checkArgumentNonnegative(uid);
mPackageName = packageName;
mAttributionTag = attributionTag;
+ mDeviceId = deviceId;
}
@@ -3505,16 +3509,33 @@ public class AppOpsManager {
@IntRange(from = 0) int uid,
@Nullable String packageName,
@Nullable String attributionTag) {
+ this(uid, packageName, attributionTag,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ /**
+ * Creates a new OpEventProxyInfo.
+ *
+ * @param uid UID of the proxy app that noted the op
+ * @param packageName Package of the proxy that noted the op
+ * @param attributionTag Attribution tag of the proxy that noted the op
+ * @param deviceId Persistent device Id of the proxy that noted the op
+ *
+ * @hide
+ */
+ public OpEventProxyInfo(
+ @IntRange(from = 0) int uid,
+ @Nullable String packageName,
+ @Nullable String attributionTag,
+ @Nullable String deviceId) {
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
-
- // onConstructed(); // You can define this method to get a callback
+ this.mDeviceId = deviceId;
}
-
/**
* Copy constructor
*
@@ -3525,6 +3546,7 @@ public class AppOpsManager {
mUid = orig.mUid;
mPackageName = orig.mPackageName;
mAttributionTag = orig.mAttributionTag;
+ mDeviceId = orig.mDeviceId;
}
/**
@@ -3551,6 +3573,9 @@ public class AppOpsManager {
return mAttributionTag;
}
+ @FlaggedApi(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ public @Nullable String getDeviceId() { return mDeviceId; }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -3560,10 +3585,12 @@ public class AppOpsManager {
byte flg = 0;
if (mPackageName != null) flg |= 0x2;
if (mAttributionTag != null) flg |= 0x4;
+ if (mDeviceId != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mUid);
if (mPackageName != null) dest.writeString(mPackageName);
if (mAttributionTag != null) dest.writeString(mAttributionTag);
+ if (mDeviceId != null) dest.writeString(mDeviceId);
}
@Override
@@ -3581,14 +3608,14 @@ public class AppOpsManager {
int uid = in.readInt();
String packageName = (flg & 0x2) == 0 ? null : in.readString();
String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-
+ String deviceId = (flg & 0x8) == 0 ? null : in.readString();
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
-
+ this.mDeviceId = deviceId;
// onConstructed(); // You can define this method to get a callback
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 3765c817b117..e8b57f224a0b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -196,7 +196,7 @@ interface IActivityManager {
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
- void finishAttachApplication(long startSeq);
+ void finishAttachApplication(long startSeq, long timestampApplicationOnCreateNs);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f1e44cc267d6..1df8f63aa402 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1104,6 +1104,10 @@ public final class LoadedApk {
return true;
}
+ if (mDataDir == null) {
+ return false;
+ }
+
// Temporarily disable logging of disk reads on the Looper thread as this is necessary -
// and the loader will access the directory anyway if we don't check it.
StrictMode.ThreadPolicy oldThreadPolicy = allowThreadDiskReads();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4d7e29b19771..25dbedce1394 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,7 +18,6 @@ package android.app;
import static android.annotation.Dimension.DP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
-import static android.app.Flags.updateRankingTime;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -897,15 +896,16 @@ public class Notification implements Parcelable
/**
* Sphere of visibility of this notification, which affects how and when the SystemUI reveals
* the notification's presence and contents in untrusted situations (namely, on the secure
- * lockscreen).
+ * lockscreen and during screen sharing).
*
* The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
* done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
* shown in all situations, but the contents are only available if the device is unlocked for
- * the appropriate user.
+ * the appropriate user and there is no active screen sharing session.
*
* A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
- * can be read even in an "insecure" context (that is, above a secure lockscreen).
+ * can be read even in an "insecure" context (that is, above a secure lockscreen or while
+ * screen sharing with a remote viewer).
* To modify the public version of this notification—for example, to redact some portions—see
* {@link Builder#setPublicVersion(Notification)}.
*
@@ -924,7 +924,8 @@ public class Notification implements Parcelable
public @interface Visibility {}
/**
- * Notification visibility: Show this notification in its entirety on all lockscreens.
+ * Notification visibility: Show this notification in its entirety on all lockscreens and while
+ * screen sharing.
*
* {@see #visibility}
*/
@@ -932,14 +933,16 @@ public class Notification implements Parcelable
/**
* Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
- * private information on secure lockscreens.
+ * private information on secure lockscreens. Conceal sensitive or private information while
+ * screen sharing.
*
* {@see #visibility}
*/
public static final int VISIBILITY_PRIVATE = 0;
/**
- * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
+ * Notification visibility: Do not reveal any part of this notification on a secure lockscreen
+ * or while screen sharing.
*
* {@see #visibility}
*/
@@ -2596,7 +2599,7 @@ public class Notification implements Parcelable
public Notification()
{
this.when = System.currentTimeMillis();
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
@@ -2612,7 +2615,7 @@ public class Notification implements Parcelable
public Notification(Context context, int icon, CharSequence tickerText, long when,
CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
{
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
@@ -2645,7 +2648,7 @@ public class Notification implements Parcelable
this.icon = icon;
this.tickerText = tickerText;
this.when = when;
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
@@ -5981,21 +5984,22 @@ public class Notification implements Parcelable
}
if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
- contentView.setLong(R.id.chronometer, "setBase",
- mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+ contentView.setLong(R.id.chronometer, "setBase", mN.getWhen()
+ + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
contentView.setBoolean(R.id.chronometer, "setStarted", true);
boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
contentView.setChronometerCountDown(R.id.chronometer, countsDown);
setTextViewColorSecondary(contentView, R.id.chronometer, p);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
- contentView.setLong(R.id.time, "setTime", mN.when);
+ contentView.setLong(R.id.time, "setTime", mN.getWhen());
setTextViewColorSecondary(contentView, R.id.time, p);
}
} else {
// We still want a time to be set but gone, such that we can show and hide it
// on demand in case it's a child notification without anything in the header
- contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
+ contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() :
+ mN.creationTime);
setTextViewColorSecondary(contentView, R.id.time, p);
}
}
@@ -7162,7 +7166,7 @@ public class Notification implements Parcelable
}
}
- if (!updateRankingTime()) {
+ if (!Flags.sortSectionByTime()) {
mN.creationTime = System.currentTimeMillis();
}
@@ -7615,10 +7619,29 @@ public class Notification implements Parcelable
}
/**
+ * Returns #when, unless it's set to 0, which should be shown as/treated as a 'current'
+ * notification. 0 is treated as a special value because it was special in an old version of
+ * android, and some apps are still (incorrectly) using it.
+ *
+ * @hide
+ */
+ public long getWhen() {
+ if (Flags.sortSectionByTime()) {
+ if (when == 0) {
+ return creationTime;
+ }
+ }
+ return when;
+ }
+
+ /**
* @return true if the notification will show the time; false otherwise
* @hide
*/
public boolean showsTime() {
+ if (Flags.sortSectionByTime()) {
+ return extras.getBoolean(EXTRA_SHOW_WHEN);
+ }
return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
}
@@ -7627,6 +7650,9 @@ public class Notification implements Parcelable
* @hide
*/
public boolean showsChronometer() {
+ if (Flags.sortSectionByTime()) {
+ return extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
+ }
return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
}
@@ -9770,6 +9796,12 @@ public class Notification implements Parcelable
* You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
* <p>
*
+ * <p>
+ * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
+ * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
+ * notifications.
+ * <p>
+ *
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* <pre class="prettyprint">
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index f092945a5d28..726064e39778 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1197,6 +1197,25 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
/**
* Callback called when a particular foreground service type has timed out.
*
+ * <p>This callback is meant to give the app a small grace period of a few seconds to finish
+ * the foreground service of the associated type - if it fails to do so, the app will be
+ * declared an ANR.
+ *
+ * <p>The foreground service of the associated type can be stopped within the time limit by
+ * {@link android.app.Service#stopSelf()},
+ * {@link android.content.Context#stopService(android.content.Intent)} or their overloads.
+ * {@link android.app.Service#stopForeground(int)} can be used as well, which demotes the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The specific time limit for each type (if one exists) is mentioned in the documentation
+ * for that foreground service type. See
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC dataSync} for example.
+ *
+ * <p>Note: time limits are restricted to a rolling 24-hour window - for example, if a
+ * foreground service type has a time limit of 6 hours, that time counter begins as soon as the
+ * foreground service starts. This time limit will only be reset once every 24 hours or if the
+ * app comes into the foreground state.
+ *
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
* the service started.
* @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index e4425ca1e8a5..8c4667f15299 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -40,3 +40,13 @@ flag {
description: "Add a new callback in Service to indicate a FGS has reached its timeout."
bug: "317799821"
}
+
+flag {
+ namespace: "system_performance"
+ name: "app_start_info_timestamps"
+ description: "Additional timestamps."
+ bug: "287153617"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 7ac95475d24b..4f062090ca40 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1348,6 +1348,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
/**
+ * This change id restricts treatments that force a given min aspect ratio to
+ * only when an app is connected to the camera
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA = 325586858L; // buganizer id
+
+ /**
* This change id restricts treatments that force a given min aspect ratio to activities
* whose orientation is fixed to portrait.
*
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index c62680f7354e..027d1015a4b5 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -92,6 +92,7 @@ public class BiometricTestSession implements AutoCloseable {
mTestedUsers = new ArraySet<>();
mUsersCleaningUp = new ArraySet<>();
setTestHalEnabled(true);
+ Log.d(getTag(), "Opening BiometricTestSession");
}
/**
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c6a8762e4d79..342479bc159e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5062,21 +5062,29 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>The version of the session configuration query
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * APIs.</p>
* <p>The possible values in this key correspond to the values defined in
* android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
* camera device must reliably report whether they are supported via
- * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
- * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
- * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
- * <p>If set to VANILLA_ICE_CREAM, the application can call
+ * It also defines the set of session specific keys in CameraCharacteristics when returned from
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }.
+ * The version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
+ * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support the
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup } API.
+ * Trying to create a CameraDeviceSetup instance throws an UnsupportedOperationException.</p>
+ * <p>From VANILLA_ICE_CREAM onwards, the camera compliance tests verify a set of
+ * commonly used SessionConfigurations to ensure that the outputs of
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * to check if the combinations of below features are supported.</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * are accurate. The application is encouraged to use these SessionConfigurations when turning on
+ * multiple features at the same time.</p>
+ * <p>When set to VANILLA_ICE_CREAM, the combinations of the following configurations are verified
+ * by the compliance tests:</p>
* <ul>
- * <li>A subset of LIMITED-level device stream combinations.</li>
- * </ul>
+ * <li>
+ * <p>A set of commonly used stream combinations:</p>
* <table>
* <thead>
* <tr>
@@ -5084,257 +5092,108 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <th style="text-align: center;">Size</th>
* <th style="text-align: center;">Target 2</th>
* <th style="text-align: center;">Size</th>
- * <th style="text-align: center;">Sample use case(s)</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">Simple preview, GPU video processing, or no-preview video recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">In-application video/image processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">Standard still imaging.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">In-app processing plus still capture.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
* <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td style="text-align: center;">YUV</td>
+ * <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Standard recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Preview plus in-app processing.</td>
+ * <td style="text-align: center;">S720P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">S720P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1080P</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">XVGA</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">S1080P_4_3</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
* </tr>
* </tbody>
* </table>
- * <pre><code>- {@code MAXIMUM} size refers to the camera device's maximum output resolution for
- * that format from {@code StreamConfigurationMap#getOutputSizes}. {@code PREVIEW} size
- * refers to the best size match to the device's screen resolution, or to 1080p
- * (@code 1920x1080}, whichever is smaller. Both sizes are guaranteed to be supported.
- *
- * - {@code S1440P} refers to {@code 1920x1440 (4:3)} and {@code 2560x1440 (16:9)}.
- * {@code S1080P} refers to {@code 1440x1080 (4:3)} and {@code 1920x1080 (16:9)}.
- * And {@code S720P} refers to {@code 960x720 (4:3)} and {@code 1280x720 (16:9)}.
- *
- * - If a combination contains a S1440P, S1080P, or S720P stream,
- * both 4:3 and 16:9 aspect ratio sizes can be queried. For example, for the
- * stream combination of {PRIV, S1440P, JPEG, MAXIMUM}, and if MAXIMUM ==
- * 4032 x 3024, the application will be able to query both
- * {PRIV, 1920 x 1440, JPEG, 4032 x 3024} and {PRIV, 2560 x 1440, JPEG, 4032 x 2268}
- * without an exception being thrown.
- * </code></pre>
* <ul>
- * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
- * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
- * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
+ * <li>{@code MAXIMUM_4_3} refers to the camera device's maximum output resolution with
+ * 4:3 aspect ratio for that format from {@code StreamConfigurationMap#getOutputSizes}.</li>
+ * <li>{@code MAXIMUM_16_9} is the maximum output resolution with 16:9 aspect ratio.</li>
+ * <li>{@code S1440P} refers to {@code 2560x1440 (16:9)}.</li>
+ * <li>{@code S1080P} refers to {@code 1920x1080 (16:9)}.</li>
+ * <li>{@code S720P} refers to {@code 1280x720 (16:9)}.</li>
+ * <li>{@code UHD} refers to {@code 3840x2160 (16:9)}.</li>
+ * <li>{@code XVGA} refers to {@code 1024x768 (4:3)}.</li>
+ * <li>{@code S1080P_43} refers to {@code 1440x1080 (4:3)}.</li>
+ * </ul>
+ * </li>
+ * <li>
+ * <p>VIDEO_STABILIZATION_MODE: {OFF, PREVIEW}</p>
+ * </li>
+ * <li>
+ * <p>AE_TARGET_FPS_RANGE: { {*, 30}, {*, 60} }</p>
+ * </li>
+ * <li>
+ * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p>
+ * </li>
* </ul>
+ * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
+ * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
+ * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
* <p>This key is available on all devices.</p>
*/
@PublicKey
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index eb644e8cfa01..dfbf06b20e2c 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -917,8 +917,11 @@ public final class CameraExtensionCharacteristics {
* image. For example, it can be used as a temporary placeholder for the requested capture
* while the final image is being processed. The supported sizes for a still capture's postview
* can be retrieved using
- * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
- * The formats of the still capture and postview should be equivalent upon capture request.</p>
+ * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.</p>
+ *
+ * <p>Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * the formats of the still capture and postview are not required to be equivalent upon capture
+ * request.</p>
*
* @param extension the extension type
* @return {@code true} in case postview is supported, {@code false} otherwise
@@ -976,8 +979,7 @@ public final class CameraExtensionCharacteristics {
*
* @param extension the extension type
* @param captureSize size of the still capture for which the postview is requested
- * @param format device-specific extension output format of the still capture and
- * postview
+ * @param format device-specific extension output format of the postview
* @return non-modifiable list of available sizes or an empty list if the format and
* size is not supported.
* @throws IllegalArgumentException in case of unsupported extension or if postview
@@ -1018,8 +1020,8 @@ public final class CameraExtensionCharacteristics {
}
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
extender.init(mCameraId, mCharacteristicsMapNative);
- return generateSupportedSizes(extender.getSupportedPostviewResolutions(
- sz), format, streamMap);
+ return getSupportedSizes(extender.getSupportedPostviewResolutions(sz),
+ format);
} else {
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
initializeExtension(extension);
@@ -1034,15 +1036,13 @@ public final class CameraExtensionCharacteristics {
}
if (format == ImageFormat.YUV_420_888) {
- return generateSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- format, streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG) {
// The framework will perform the additional encoding pass on the
// processed YUV_420 buffers.
- return generateJpegSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
// Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
// extension case
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 875550aea5f5..a10e2505758e 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -36,6 +36,8 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
@@ -57,6 +59,8 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
private android.hardware.camera2.extension.Size mResolution = null;
private android.hardware.camera2.extension.Size mPostviewResolution = null;
private int mFormat = -1;
+ private int mPostviewFormat = -1;
+ private int mCaptureFormat = -1;
private Surface mOutputSurface = null;
private ImageWriter mOutputWriter = null;
private Surface mPostviewOutputSurface = null;
@@ -204,10 +208,12 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
}
public void onOutputSurface(Surface surface, int format) throws RemoteException {
- if (format != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && format != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + format);
return;
}
+ CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(surface);
+ mCaptureFormat = surfaceInfo.mFormat;
mOutputSurface = surface;
initializePipeline();
}
@@ -215,10 +221,11 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
public void onPostviewOutputSurface(Surface surface) throws RemoteException {
CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
CameraExtensionUtils.querySurface(surface);
- if (postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
return;
}
+ mPostviewFormat = postviewSurfaceInfo.mFormat;
mPostviewOutputSurface = surface;
initializePostviewPipeline();
}
@@ -233,7 +240,7 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
}
public void onImageFormatUpdate(int format) throws RemoteException {
- if (format != ImageFormat.YUV_420_888) {
+ if (!Flags.extension10Bit() && format != ImageFormat.YUV_420_888) {
Log.e(TAG, "Unsupported input format: " + format);
return;
}
@@ -244,33 +251,45 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
private void initializePipeline() throws RemoteException {
if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
(mYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
- mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
- ImageFormat.JPEG,
- (mResolution.width * mResolution.height * 3)/2 + JPEG_APP_SEGMENT_SIZE, 1);
- mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
- JPEG_QUEUE_SIZE);
- mYuvReader.setOnImageAvailableListener(
- new YuvCallback(mYuvReader, mOutputWriter), mHandler);
- mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ if (Flags.extension10Bit() && mCaptureFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is JPEG and capture is YUV
+ mProcessor.onOutputSurface(mOutputSurface, mCaptureFormat);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
+ mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
+ ImageFormat.JPEG,
+ (mResolution.width * mResolution.height * 3) / 2
+ + JPEG_APP_SEGMENT_SIZE, 1);
+ mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height,
+ mFormat, JPEG_QUEUE_SIZE);
+ mYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mYuvReader, mOutputWriter), mHandler);
+ mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
private void initializePostviewPipeline() throws RemoteException {
if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
&& (mPostviewYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1
- mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface, 1/*maxImages*/,
- ImageFormat.JPEG, mPostviewResolution.width * mPostviewResolution.height, 1);
- mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
- mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
- mPostviewYuvReader.setOnImageAvailableListener(
- new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
- mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ if (Flags.extension10Bit() && mPostviewFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is YUV and capture is JPEG
+ mProcessor.onPostviewOutputSurface(mPostviewOutputSurface);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1
+ mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface,
+ 1/*maxImages*/, ImageFormat.JPEG,
+ mPostviewResolution.width * mPostviewResolution.height, 1);
+ mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
+ mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
+ mPostviewYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
+ mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c00e6101b363..3ae319999e35 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -390,7 +390,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
if (surfaceInfo.mFormat == ImageFormat.JPEG) {
mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
mImageProcessor = mImageJpegProcessor;
+ } else if (Flags.extension10Bit() && mClientPostviewSurface != null) {
+ // Handles case when postview is JPEG and capture is YUV
+ CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+ CameraExtensionUtils.querySurface(mClientPostviewSurface);
+ if (postviewSurfaceInfo.mFormat == ImageFormat.JPEG) {
+ mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
+ mImageProcessor = mImageJpegProcessor;
+ }
}
+
mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
mImageExtender.getMaxCaptureStage());
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f0c6e2e4e123..40f047732c06 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -112,19 +112,30 @@ public final class CameraExtensionUtils {
if (outputConfig == null) return null;
SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
- if (surfaceInfo.mFormat == captureFormat) {
- if (supportedPostviewSizes.containsKey(captureFormat)) {
- Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
- if (supportedPostviewSizes.get(surfaceInfo.mFormat)
- .contains(postviewSize)) {
- return outputConfig.getSurface();
- } else {
- throw new IllegalArgumentException("Postview size not supported!");
- }
+
+ if (Flags.extension10Bit()) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
}
} else {
- throw new IllegalArgumentException("Postview format should be equivalent to " +
- " the capture format!");
+ if (surfaceInfo.mFormat == captureFormat) {
+ if (supportedPostviewSizes.containsKey(captureFormat)) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Postview format should be equivalent to "
+ + " the capture format!");
+ }
}
return null;
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6b5e17d8563d..b58830861c5e 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -180,3 +180,11 @@ flag {
description: "Use runtime permission state to determine appop state"
bug: "266164193"
}
+
+flag {
+ name: "device_id_in_op_proxy_info_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable getDeviceId API in OpEventProxyInfo"
+ bug: "337340961"
+} \ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 72ab970b4b80..e6ddf3556490 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1736,6 +1736,24 @@ public final class Settings {
"android.settings.NETWORK_OPERATOR_SETTINGS";
/**
+ * Activity Action: Show settings for selecting the network provider.
+ * <p>
+ * In some cases, a matching Activity may not be provided, so ensure you
+ * safeguard against this.
+ * <p>
+ * Access to this preference can be customized via Settings' app.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for selection of 2G/3G.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 997c958187fe..5f6bdbf193b9 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1383,16 +1383,22 @@ public class DreamService extends Service implements Window.Callback {
DreamService.DREAM_META_DATA, DREAM_META_DATA_ROOT_TAG,
com.android.internal.R.styleable.Dream)) {
if (rawMetadata == null) return null;
- return new DreamMetadata(
- convertToComponentName(
- rawMetadata.getString(
- com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
- rawMetadata.getDrawable(
- com.android.internal.R.styleable.Dream_previewImage),
- rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
- DEFAULT_SHOW_COMPLICATIONS),
- rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
- );
+ try {
+ return new DreamMetadata(
+ convertToComponentName(
+ rawMetadata.getString(
+ com.android.internal.R.styleable.Dream_settingsActivity),
+ serviceInfo),
+ rawMetadata.getDrawable(
+ com.android.internal.R.styleable.Dream_previewImage),
+ rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+ DEFAULT_SHOW_COMPLICATIONS),
+ rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
+ );
+ } catch (Exception exception) {
+ Log.e(TAG, "Failed to create read metadata", exception);
+ return null;
+ }
}
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b19c6d3..c674968bba8a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -70,6 +70,11 @@ import java.util.Locale;
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+
+ // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h
+ private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 4f;
+ private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0.2f;
+
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -494,9 +499,9 @@ public abstract class Layout {
drawText(canvas, firstLine, lastLine);
- // Since high contrast text draws a solid rectangle background behind the text, it covers up
- // the highlights and selections. In this case we draw over the top of the text with a
- // blend mode that ensures the text stays high-contrast.
+ // Since high contrast text draws a thick border on the text, the highlight actually makes
+ // it harder to read. In this case we draw over the top of the text with a blend mode that
+ // ensures the text stays high-contrast.
if (shouldDrawHighlightsOnTop(canvas)) {
drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
cursorOffsetVertical, firstLine, lastLine);
@@ -922,6 +927,9 @@ public abstract class Layout {
public void drawBackground(
@NonNull Canvas canvas,
int firstLine, int lastLine) {
+
+ drawHighContrastBackground(canvas, firstLine, lastLine);
+
// First, draw LineBackgroundSpans.
// LineBackgroundSpans know nothing about the alignment, margins, or
// direction of the layout or line. XXX: Should they?
@@ -988,6 +996,66 @@ public abstract class Layout {
}
/**
+ * Draws a solid rectangle behind the text, the same color as the high contrast stroke border,
+ * to make it even easier to read.
+ *
+ * <p>We draw it here instead of in DrawTextFunctor so that multiple spans don't draw
+ * backgrounds over each other's text.
+ */
+ private void drawHighContrastBackground(@NonNull Canvas canvas, int firstLine, int lastLine) {
+ if (!shouldDrawHighlightsOnTop(canvas)) {
+ return;
+ }
+
+ var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
+ mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
+
+ var bgPaint = mWorkPlainPaint;
+ bgPaint.reset();
+ bgPaint.setColor(isHighContrastTextDark() ? Color.WHITE : Color.BLACK);
+ bgPaint.setStyle(Paint.Style.FILL);
+
+ int start = getLineStart(firstLine);
+ int end = getLineEnd(lastLine);
+ // Draw a separate background rectangle for each line of text, that only surrounds the
+ // characters on that line.
+ forEachCharacterBounds(
+ start,
+ end,
+ firstLine,
+ lastLine,
+ new CharacterBoundsListener() {
+ int mLastLineNum = -1;
+ final RectF mLineBackground = new RectF();
+
+ @Override
+ public void onCharacterBounds(int index, int lineNum, float left, float top,
+ float right, float bottom) {
+ if (lineNum != mLastLineNum) {
+ drawRect();
+ mLineBackground.set(left, top, right, bottom);
+ mLastLineNum = lineNum;
+ } else {
+ mLineBackground.union(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void onEnd() {
+ drawRect();
+ }
+
+ private void drawRect() {
+ if (!mLineBackground.isEmpty()) {
+ mLineBackground.inset(-padding, -padding);
+ canvas.drawRect(mLineBackground, bgPaint);
+ }
+ }
+ }
+ );
+ }
+
+ /**
* @param canvas
* @return The range of lines that need to be drawn, possibly empty.
* @hide
@@ -1682,7 +1750,7 @@ public abstract class Layout {
}
if (bounds == null) {
- throw new IllegalArgumentException("bounds can't be null.");
+ throw new IllegalArgumentException("bounds can't be null.");
}
final int neededLength = 4 * (end - start);
@@ -1698,6 +1766,34 @@ public abstract class Layout {
final int startLine = getLineForOffset(start);
final int endLine = getLineForOffset(end - 1);
+
+ forEachCharacterBounds(start, end, startLine, endLine,
+ (index, lineNum, left, lineTop, right, lineBottom) -> {
+ final int boundsIndex = boundsStart + 4 * (index - start);
+ bounds[boundsIndex] = left;
+ bounds[boundsIndex + 1] = lineTop;
+ bounds[boundsIndex + 2] = right;
+ bounds[boundsIndex + 3] = lineBottom;
+ });
+ }
+
+ /**
+ * Return the characters' bounds in the given range. The coordinates are in local text layout.
+ *
+ * @param start the start index to compute the character bounds, inclusive.
+ * @param end the end index to compute the character bounds, exclusive.
+ * @param startLine index of the line that contains {@code start}
+ * @param endLine index of the line that contains {@code end}
+ * @param listener called for each character with its bounds
+ *
+ */
+ private void forEachCharacterBounds(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @IntRange(from = 0) int startLine,
+ @IntRange(from = 0) int endLine,
+ CharacterBoundsListener listener
+ ) {
float[] horizontalBounds = null;
for (int line = startLine; line <= endLine; ++line) {
final int lineStart = getLineStart(line);
@@ -1722,13 +1818,10 @@ public abstract class Layout {
final float left = horizontalBounds[offset * 2] + lineStartPos;
final float right = horizontalBounds[offset * 2 + 1] + lineStartPos;
- final int boundsIndex = boundsStart + 4 * (index - start);
- bounds[boundsIndex] = left;
- bounds[boundsIndex + 1] = lineTop;
- bounds[boundsIndex + 2] = right;
- bounds[boundsIndex + 3] = lineBottom;
+ listener.onCharacterBounds(index, line, left, lineTop, right, lineBottom);
}
}
+ listener.onEnd();
}
/**
@@ -4443,4 +4536,15 @@ public abstract class Layout {
public Paint.FontMetrics getMinimumFontMetrics() {
return mMinimumFontMetrics;
}
+
+ /**
+ * Callback for {@link #forEachCharacterBounds(int, int, int, int, CharacterBoundsListener)}
+ */
+ private interface CharacterBoundsListener {
+ void onCharacterBounds(int index, int lineNum, float left, float top, float right,
+ float bottom);
+
+ /** Called after the last character has been sent to {@link #onCharacterBounds}. */
+ default void onEnd() {}
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ddead88b128f..f4d240882e93 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -912,12 +912,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final String AUTOFILL_LOG_TAG = "View.Autofill";
/**
- * The logging tag used by this class when logging verbose and chatty (high volume)
- * autofill-related messages.
- */
- private static final String AUTOFILL_CHATTY_LOG_TAG = "View.Autofill.Chatty";
-
- /**
* The logging tag used by this class when logging content capture-related messages.
*/
private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
@@ -8708,8 +8702,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@CallSuper
protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
@Nullable Rect previouslyFocusedRect) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ gainFocus);
}
if (gainFocus) {
@@ -8777,8 +8771,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (canNotifyAutofillEnterExitEvent()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, this + " afm is not null");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, this + " afm is not null");
}
if (enter) {
// We have not been laid out yet, hence cannot evaluate
@@ -8791,8 +8785,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// animation beginning. On the time, the view is not visible
// to the user. And then as the animation progresses, the view
// becomes visible to the user.
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG,
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG,
"notifyEnterOrExitForAutoFillIfNeeded:"
+ " isLaidOut(): " + isLaidOut()
+ " isVisibleToUser(): " + isVisibleToUser()
@@ -11024,28 +11018,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private boolean isAutofillable() {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "isAutofillable() entered.");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
}
if (getAutofillType() == AUTOFILL_TYPE_NONE) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
}
return false;
}
final AutofillManager afm = getAutofillManager();
if (afm == null) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "AutofillManager is null");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "AutofillManager is null");
}
return false;
}
// Check whether view is not part of an activity. If it's not, return false.
if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
}
return false;
}
@@ -11056,9 +11050,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
|| (!isImportantForAutofill()
&& afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG,
- "isImportantForAutofill(): " + isImportantForAutofill()
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill()
+ "afm.isAutofillable(): " + afm.isAutofillable(this));
}
return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
@@ -11066,8 +11059,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// If the previous condition is not met, fall back to the previous way to trigger fill
// request based on autofill importance instead.
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
}
return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
}
@@ -11083,8 +11076,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/** @hide */
public boolean canNotifyAutofillEnterExitEvent() {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ " isAutofillable(): " + isAutofillable()
+ " isAttachedToWindow(): " + isAttachedToWindow());
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c7ca16b6d1bd..f51d909e9e8b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4257,8 +4257,8 @@ public final class ViewRootImpl implements ViewParent,
// when the values are applicable.
if (mDrawnThisFrame) {
mDrawnThisFrame = false;
- updateInfrequentCount();
setCategoryFromCategoryCounts();
+ updateInfrequentCount();
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
if (!mIsFrameRateConflicted) {
@@ -4276,6 +4276,10 @@ public final class ViewRootImpl implements ViewParent,
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ } else if (mPreferredFrameRate == 0) {
+ // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
+ setPreferredFrameRate(0);
+ mPreferredFrameRate = -1;
}
}
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 98ff3c6bc347..cd13c4abac0b 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
@@ -2,6 +2,13 @@ package: "com.android.window.flags"
container: "system"
flag {
+ name: "disable_thin_letterboxing_reachability"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether reachability is disabled in case of thin letterboxing"
+ bug: "334077350"
+}
+
+flag {
name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
namespace: "large_screen_experiences_app_compat"
description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 7c7c7b8fa51d..9f9aae53e0af 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -473,7 +473,14 @@ public final class PowerStats {
} finally {
// Unconditionally skip to the end of the written data, even if the actual parcel
// format is incompatible
- parcel.setDataPosition(endPos);
+ if (endPos > parcel.dataPosition()) {
+ if (endPos >= parcel.dataSize()) {
+ throw new IndexOutOfBoundsException(
+ "PowerStats end position: " + endPos + " is outside the parcel bounds: "
+ + parcel.dataSize());
+ }
+ parcel.setDataPosition(endPos);
+ }
}
}
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 69d3d6a6d521..c21a43e807a9 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -53,8 +53,6 @@ public class ScreenshotHelper {
public ScreenshotHelper(Context context) {
mContext = context;
- IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED);
- mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
/**
@@ -108,6 +106,8 @@ public class ScreenshotHelper {
public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(ACTION_USER_SWITCHED), Context.RECEIVER_EXPORTED);
final Runnable mScreenshotTimeout = () -> {
synchronized (mScreenshotLock) {
@@ -223,6 +223,11 @@ public class ScreenshotHelper {
mScreenshotConnection = null;
mScreenshotService = null;
}
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Attempted to remove broadcast receiver twice");
+ }
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2106c53f565..77a99120072e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8233,7 +8233,7 @@
</activity>
<activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
android:exported="false"
- android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:theme="@style/AccessibilityButtonChooserDialog"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d1be3814cb5..50ed4228fa92 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -764,6 +764,9 @@
<!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
<bool name="config_unfoldTransitionHingeAngle">false</bool>
+ <!-- Indicates whether to enable haptics during unfold animation -->
+ <bool name="config_unfoldTransitionHapticsEnabled">false</bool>
+
<!-- Indicates the time needed to time out the fold animation if the device stops in half folded
mode. -->
<integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5945f81704c5..a46dc045269d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1685,4 +1685,9 @@ please see styles_device_defaults.xml.
<item name="android:paddingStart">8dp</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
</style>
+
+ <style name="AccessibilityButtonChooserDialog"
+ parent="@style/Theme.DeviceDefault.Resolver">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daf12301ea0f..d432c05d199f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4219,6 +4219,7 @@
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" />
<java-symbol type="bool" name="config_unfoldTransitionHingeAngle" />
+ <java-symbol type="bool" name="config_unfoldTransitionHapticsEnabled" />
<java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5cc1ee46f61c..7fb894a9dbe9 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -86,6 +86,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -126,6 +127,7 @@ public class NotificationTest {
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -1835,6 +1837,36 @@ public class NotificationTest {
Assert.assertEquals(bitmap, resultBitmap);
}
+ @Test
+ public void testGetWhen_zero() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setWhen(0)
+ .build();
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(0);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(n.creationTime);
+ }
+
+ @Test
+ public void testGetWhen_devProvidedNonZero() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setWhen(9)
+ .build();
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(9);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(9);
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index f60eff69690a..1c1236279b61 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -18,9 +18,6 @@ package android.text;
import static com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,6 +44,8 @@ import android.text.style.StrikethroughSpan;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -63,7 +62,13 @@ public class LayoutTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final Expect expect = Expect.create();
+
+ // Line count when using MockLayout
private static final int LINE_COUNT = 5;
+ // Actual line count when using StaticLayout
+ private static final int STATIC_LINE_COUNT = 9;
private static final int LINE_HEIGHT = 12;
private static final int LINE_DESCENT = 4;
private static final CharSequence LAYOUT_TEXT = "alwei\t;sdfs\ndf @";
@@ -655,8 +660,8 @@ public class LayoutTest {
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabled_testDrawSelectionAndHighlight_drawsHighContrastSelectionAndHighlight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -677,9 +682,12 @@ public class LayoutTest {
layout.draw(c, highlightPaths, highlightPaints, selectionPath, selectionPaint,
/* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 2;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -687,29 +695,26 @@ public class LayoutTest {
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
- assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ expect.that(drawCommand.paint.getBlendMode()).isNotNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn on top of text")
+ expect.withMessage("highlight is drawn on top of text")
.that(highlightsFound).isEqualTo(0);
}
}
- assertThat(highlightsFound).isEqualTo(2);
+ expect.that(highlightsFound).isEqualTo(2);
}
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabled_testDrawHighlight_drawsHighContrastHighlight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -730,9 +735,12 @@ public class LayoutTest {
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -740,29 +748,26 @@ public class LayoutTest {
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
- assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ expect.that(drawCommand.paint.getBlendMode()).isNotNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn on top of text")
+ expect.withMessage("highlight is drawn on top of text")
.that(highlightsFound).isEqualTo(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextDisabledByDefault_testDrawHighlight_drawsNormalHighlightBehind() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -782,9 +787,12 @@ public class LayoutTest {
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = 0;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -792,29 +800,26 @@ public class LayoutTest {
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
- assertThat(drawCommand.paint.getBlendMode()).isNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ expect.that(drawCommand.paint.getBlendMode()).isNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn behind text")
+ expect.withMessage("highlight is drawn behind text")
.that(highlightsFound).isGreaterThan(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
@RequiresFlagsDisabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabledButFlagOff_testDrawHighlight_drawsNormalHighlightBehind() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -835,9 +840,12 @@ public class LayoutTest {
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = 0;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -845,33 +853,84 @@ public class LayoutTest {
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
- assertThat(drawCommand.paint.getBlendMode()).isNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ expect.that(drawCommand.paint.getBlendMode()).isNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn behind text")
+ expect.withMessage("highlight is drawn behind text")
.that(highlightsFound).isGreaterThan(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
public void mockCanvasHighContrastOverridesCorrectly() {
var canvas = new MockCanvas(100, 100);
- assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ expect.that(canvas.isHighContrastTextEnabled()).isFalse();
canvas.setHighContrastTextEnabled(true);
- assertThat(canvas.isHighContrastTextEnabled()).isTrue();
+ expect.that(canvas.isHighContrastTextEnabled()).isTrue();
canvas.setHighContrastTextEnabled(false);
- assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ expect.that(canvas.isHighContrastTextEnabled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testDrawLightText_drawsBlackBackgroundRects() {
+ mTextPaint.setColor(Color.parseColor("#CCAA33"));
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = STATIC_LINE_COUNT;
+ var highlightsDrawn = 0;
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
+
+ int numBackgroundsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.rect != null) {
+ numBackgroundsFound++;
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(drawCommand.rect.height()).isAtLeast(LINE_HEIGHT);
+ expect.that(drawCommand.rect.width()).isGreaterThan(0);
+ float expectedY = (numBackgroundsFound) * (LINE_HEIGHT + LINE_DESCENT);
+ expect.that(drawCommand.rect.bottom).isAtLeast(expectedY);
+ } else if (drawCommand.text != null) {
+ // draw text
+ curLineIndex++;
+
+ expect.withMessage("background is drawn on top of text")
+ .that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
+ } else {
+ fail("unexpected path drawn");
+ }
+ }
+
+ // One for each line
+ expect.that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
}
private static final class MockCanvas extends Canvas {
@@ -881,22 +940,46 @@ public class LayoutTest {
public final float x;
public final float y;
public final Path path;
+ public final RectF rect;
public final Paint paint;
DrawCommand(String text, float x, float y, Paint paint) {
this.text = text;
this.x = x;
this.y = y;
- this.paint = paint;
+ this.paint = new Paint(paint);
path = null;
+ rect = null;
}
DrawCommand(Path path, Paint paint) {
this.path = path;
- this.paint = paint;
+ this.paint = new Paint(paint);
y = 0;
x = 0;
text = null;
+ rect = null;
+ }
+
+ DrawCommand(RectF rect, Paint paint) {
+ this.rect = new RectF(rect);
+ this.paint = new Paint(paint);
+ path = null;
+ y = 0;
+ x = 0;
+ text = null;
+ }
+
+ @Override
+ public String toString() {
+ return "DrawCommand{"
+ + "text='" + text + '\''
+ + ", x=" + x
+ + ", y=" + y
+ + ", path=" + path
+ + ", rect=" + rect
+ + ", paint=" + paint
+ + '}';
}
}
@@ -956,6 +1039,11 @@ public class LayoutTest {
mDrawCommands.add(new DrawCommand(path, p));
}
+ @Override
+ public void drawRect(RectF rect, Paint p) {
+ mDrawCommands.add(new DrawCommand(rect, p));
+ }
+
List<DrawCommand> getDrawCommands() {
return mDrawCommands;
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index f885e31ed270..32aec1a335f2 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -36,6 +36,7 @@ import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.app.Activity;
+import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@@ -47,6 +48,7 @@ import android.widget.FrameLayout;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -537,6 +539,28 @@ public class ViewFrameRateTest {
});
waitForAfterDraw();
}
+
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void frameRateReset() throws Throwable {
+ mMovingView.setRequestedFrameRate(120f);
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> mMovingView.setVisibility(View.INVISIBLE));
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ for (int i = 0; i < 120; i++) {
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.getParent().onDescendantInvalidated(mMovingView, mMovingView);
+ });
+ instrumentation.waitForIdleSync();
+ }
+
+ assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 5caf77dbdcff..7c098f2a63f3 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1138,6 +1138,8 @@ public class ViewRootImplTest {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = sContext.getSystemService(WindowManager.class);
@@ -1157,8 +1159,6 @@ public class ViewRootImplTest {
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
runAfterDraw(() -> assertEquals(expected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
@@ -1168,8 +1168,6 @@ public class ViewRootImplTest {
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
runAfterDraw(() -> assertEquals(expected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
@@ -1177,12 +1175,26 @@ public class ViewRootImplTest {
// Infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
+
+ // When the View vote, it's still considered as intermittent update state
+ sInstrumentation.runOnMainSync(() -> {
+ mView.invalidate();
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
+ });
+ waitForAfterDraw();
+
+ // Becomes frequent update state
+ sInstrumentation.runOnMainSync(() -> {
+ mView.invalidate();
+ runAfterDraw(() -> assertEquals(expected,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
+ });
}
/**
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 6402206410b5..baab3b218746 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -168,6 +168,20 @@ public class PowerStatsTest {
assertThat(end).isEqualTo("END");
}
+ @Test
+ public void parceling_corruptParcel() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+ newParcel.writeInt(-42); // Negative section length
+ newParcel.setDataPosition(0);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+ }
+
private static Parcel marshallAndUnmarshall(Parcel parcel) {
byte[] bytes = parcel.marshall();
parcel.recycle();
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index e1bf40ca19dc..611013365e7d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -105,18 +105,21 @@ class BubbleExpandedViewPinControllerTest {
}
@Test
- fun onDragUpdate_stayOnSameSide() {
+ fun drag_stayOnSameSide() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onDragEnd()
}
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeft() {
+ fun drag_toLeft() {
+ // Drag to left, but don't finish
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -132,10 +135,16 @@ class BubbleExpandedViewPinControllerTest {
.isEqualTo(expectedDropTargetBounds.height())
assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Finish the drag
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT)
}
@Test
- fun onDragUpdate_toLeftAndBackToRight() {
+ fun drag_toLeftAndBackToRight() {
+ // Drag to left
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -143,6 +152,7 @@ class BubbleExpandedViewPinControllerTest {
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
+ // Drag to right
runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
// We have to wait for existing drop target to animate out and new to animate in
waitForAnimateOut()
@@ -158,10 +168,15 @@ class BubbleExpandedViewPinControllerTest {
assertThat(testListener.locationChanges)
.containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Release the view
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeftInExclusionRect() {
+ fun drag_toLeftInExclusionRect() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
// Exclusion rect is around the bottom center area of the screen
@@ -170,6 +185,10 @@ class BubbleExpandedViewPinControllerTest {
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
@@ -256,8 +275,13 @@ class BubbleExpandedViewPinControllerTest {
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
val locationChanges = mutableListOf<BubbleBarLocation>()
+ val locationReleases = mutableListOf<BubbleBarLocation>()
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
+
+ override fun onRelease(location: BubbleBarLocation) {
+ locationReleases.add(location)
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 0297901c8921..f9a1d940c734 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -82,8 +82,8 @@ public class BadgedImageView extends ConstraintLayout {
private BubbleViewProvider mBubble;
private BubblePositioner mPositioner;
- private boolean mOnLeft;
-
+ private boolean mBadgeOnLeft;
+ private boolean mDotOnLeft;
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
@@ -153,7 +153,8 @@ public class BadgedImageView extends ConstraintLayout {
public void hideDotAndBadge(boolean onLeft) {
addDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
- mOnLeft = onLeft;
+ mBadgeOnLeft = onLeft;
+ mDotOnLeft = onLeft;
hideBadge();
}
@@ -185,7 +186,7 @@ public class BadgedImageView extends ConstraintLayout {
mDrawParams.dotColor = mDotColor;
mDrawParams.iconBounds = mTempBounds;
- mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.leftAlign = mDotOnLeft;
mDrawParams.scale = mDotScale;
mDotRenderer.draw(canvas, mDrawParams);
@@ -255,7 +256,7 @@ public class BadgedImageView extends ConstraintLayout {
* Whether decorations (badges or dots) are on the left.
*/
boolean getDotOnLeft() {
- return mOnLeft;
+ return mDotOnLeft;
}
/**
@@ -263,7 +264,7 @@ public class BadgedImageView extends ConstraintLayout {
*/
float[] getDotCenter() {
float[] dotPosition;
- if (mOnLeft) {
+ if (mDotOnLeft) {
dotPosition = mDotRenderer.getLeftDotPosition();
} else {
dotPosition = mDotRenderer.getRightDotPosition();
@@ -291,22 +292,23 @@ public class BadgedImageView extends ConstraintLayout {
if (onLeft != getDotOnLeft()) {
if (shouldDrawDot()) {
animateDotScale(0f /* showDot */, () -> {
- mOnLeft = onLeft;
+ mDotOnLeft = onLeft;
invalidate();
animateDotScale(1.0f, null /* after */);
});
} else {
- mOnLeft = onLeft;
+ mDotOnLeft = onLeft;
}
}
+ mBadgeOnLeft = onLeft;
// TODO animate badge
showBadge();
-
}
/** Sets the position of the dot and badge. */
void setDotBadgeOnLeft(boolean onLeft) {
- mOnLeft = onLeft;
+ mBadgeOnLeft = onLeft;
+ mDotOnLeft = onLeft;
invalidate();
showBadge();
}
@@ -361,7 +363,7 @@ public class BadgedImageView extends ConstraintLayout {
}
int translationX;
- if (mOnLeft) {
+ if (mBadgeOnLeft) {
translationX = -(mBubble.getBubbleIcon().getWidth() - appBadgeBitmap.getWidth());
} else {
translationX = 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d2958779c0d4..edd5935b508d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -474,11 +474,16 @@ public class BubbleController implements ConfigurationChangeListener,
mDisplayController.addDisplayChangingController(
(displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
- // This is triggered right before the rotation is applied
- if (fromRotation != toRotation) {
+ Rect newScreenBounds = new Rect();
+ if (newDisplayAreaInfo != null) {
+ newScreenBounds =
+ newDisplayAreaInfo.configuration.windowConfiguration.getBounds();
+ }
+ // This is triggered right before the rotation or new screen size is applied
+ if (fromRotation != toRotation || !newScreenBounds.equals(mScreenBounds)) {
if (mStackView != null) {
// Layout listener set on stackView will update the positioner
- // once the rotation is applied
+ // once the rotation or screen change is applied
mStackView.onOrientationChanged();
}
}
@@ -725,6 +730,17 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ /**
+ * Animate bubble bar to the given location. The location change is transient. It does not
+ * update the state of the bubble bar.
+ * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ */
+ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ if (canShowAsBubbleBar()) {
+ mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
+ }
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -2250,15 +2266,19 @@ public class BubbleController implements ConfigurationChangeListener,
private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
private final Bubbles.BubbleStateListener mBubbleListener =
new Bubbles.BubbleStateListener() {
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
- @Override
- public void onBubbleStateChange(BubbleBarUpdate update) {
- Bundle b = new Bundle();
- b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
- b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
- mListener.call(l -> l.onBubbleStateChange(b));
- }
- };
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ mListener.call(l -> l.animateBubbleBarLocation(location));
+ }
+ };
IBubblesImpl(BubbleController controller) {
mController = controller;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 127a49fc7875..322088b17e63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,6 +37,7 @@ import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -304,6 +305,12 @@ public interface Bubbles {
* Called when the bubbles state changes.
*/
void onBubbleStateChange(BubbleBarUpdate update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(BubbleBarLocation location);
}
/** Listener to find out about stack expansion / collapse events. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index e48f8d5f1c84..14d29cd887bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -15,8 +15,9 @@
*/
package com.android.wm.shell.bubbles;
-import android.os.Bundle;
+import android.os.Bundle;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
* Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
*/
@@ -26,4 +27,10 @@ oneway interface IBubblesListener {
* Called when the bubbles state changes.
*/
void onBubbleStateChange(in Bundle update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(in BubbleBarLocation location);
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fe9c4d4c9094..a51ac633ad86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -135,9 +135,9 @@ class BubbleBarExpandedViewDragController(
private fun finishDrag() {
if (!isStuckToDismiss) {
- animationHelper.animateToRestPosition()
pinController.onDragEnd()
dragListener.onReleased(inDismiss = false)
+ animationHelper.animateToRestPosition()
dismissView.hide()
}
isMoving = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62cc4da3193e..a351cef223b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -42,6 +44,8 @@ import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BaseBubblePinController;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.DismissView;
import kotlin.Unit;
@@ -115,7 +119,18 @@ public class BubbleBarLayerView extends FrameLayout
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(mBubbleController::setBubbleBarLocation);
+ mBubbleExpandedViewPinController.setListener(
+ new BaseBubblePinController.LocationChangeListener() {
+ @Override
+ public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location);
+ }
+ });
setOnClickListener(view -> hideMenuOrCollapse());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
index a008045a4b6f..e514f9d70599 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -82,6 +82,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
fun onDragEnd() {
getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } }
dismissZone = null
+ listener?.onRelease(if (onLeft) LEFT else RIGHT)
}
/**
@@ -170,14 +171,22 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
/** Receive updates on location changes */
interface LocationChangeListener {
/**
- * Bubble bar [BubbleBarLocation] has changed as a result of dragging
+ * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
+ * progress.
*
* Triggered when drag gesture passes the middle of the screen and before touch up. Can be
* triggered multiple times per gesture.
*
* @param location new location as a result of the ongoing drag operation
*/
- fun onChange(location: BubbleBarLocation)
+ fun onChange(location: BubbleBarLocation) {}
+
+ /**
+ * Bubble bar has been released in the [BubbleBarLocation].
+ *
+ * @param location final location of the bubble bar once drag is released
+ */
+ fun onRelease(location: BubbleBarLocation)
}
companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 4eff3f03670e..6e61f22ca563 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -26,6 +26,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -43,6 +44,7 @@ import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -69,9 +71,11 @@ public abstract class Pip2Module {
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
PipTouchHandler pipTouchHandler,
- @NonNull PipScheduler pipScheduler) {
+ @NonNull PipScheduler pipScheduler,
+ @NonNull PipTransitionState pipStackListenerController) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
+ pipStackListenerController);
}
@WMSingleton
@@ -85,6 +89,9 @@ public abstract class Pip2Module {
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -92,7 +99,7 @@ public abstract class Pip2Module {
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
- mainExecutor));
+ taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor));
}
}
@@ -101,8 +108,8 @@ public abstract class Pip2Module {
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
- ShellTaskOrganizer shellTaskOrganizer) {
- return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer);
+ PipTransitionState pipTransitionState) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
}
@WMSingleton
@@ -146,4 +153,10 @@ public abstract class Pip2Module {
return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
+
+ @WMSingleton
+ @Provides
+ static PipTransitionState providePipStackListenerController() {
+ return new PipTransitionState();
+ }
}
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 4f71a02528c3..7730285c86c8 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
@@ -54,7 +54,6 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -125,12 +124,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Called when the Shell wants to start resizing Pip transition/animation.
- *
- * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
- * client completes any potential draws upon WM state updates.
*/
- public void startResizeTransition(WindowContainerTransaction wct,
- Consumer<Rect> onFinishResizeCallback) {
+ public void startResizeTransition(WindowContainerTransaction wct) {
// Default implementation does nothing.
}
@@ -266,9 +261,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/** Whether a particular package is same as current pip package. */
- public boolean isInPipPackage(String packageName) {
+ public boolean isPackageActiveInPip(String packageName) {
final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
- return packageName != null && inPipTask != null
+ return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
&& packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 1e18b8c002db..a12882f56eb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -16,25 +16,31 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.InsetsState;
import android.view.SurfaceControl;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.Preconditions;
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.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -42,6 +48,8 @@ import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TaskStackListenerCallback;
+import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -57,8 +65,11 @@ import com.android.wm.shell.sysui.ShellInit;
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
+ PipTransitionState.PipTransitionStateChangedListener,
DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
+ private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
+ private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
private final Context mContext;
private final ShellController mShellController;
@@ -68,6 +79,9 @@ public class PipController implements ConfigurationChangeListener,
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipScheduler mPipScheduler;
+ private final TaskStackListenerImpl mTaskStackListener;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final PipTransitionState mPipTransitionState;
private final ShellExecutor mMainExecutor;
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
@@ -104,6 +118,9 @@ public class PipController implements ConfigurationChangeListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -113,6 +130,10 @@ public class PipController implements ConfigurationChangeListener,
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipScheduler = pipScheduler;
+ mTaskStackListener = taskStackListener;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
@@ -132,6 +153,9 @@ public class PipController implements ConfigurationChangeListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -140,7 +164,8 @@ public class PipController implements ConfigurationChangeListener,
}
return new PipController(context, shellInit, shellController, displayController,
displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
- pipScheduler, mainExecutor);
+ pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState,
+ mainExecutor);
}
private void onInit() {
@@ -164,6 +189,17 @@ public class PipController implements ConfigurationChangeListener,
mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
this::createExternalInterface, this);
mShellController.addConfigurationChangeListener(this);
+
+ mTaskStackListener.addListener(new TaskStackListenerCallback() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ return;
+ }
+ mPipScheduler.scheduleExitPipViaExpand();
+ }
+ });
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -245,11 +281,46 @@ public class PipController implements ConfigurationChangeListener,
Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onSwipePipToHomeAnimationStart: %s", componentName);
- mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds,
- overlay, appBounds);
+ Bundle extra = new Bundle();
+ extra.putParcelable(SWIPE_TO_PIP_OVERLAY, overlay);
+ extra.putParcelable(SWIPE_TO_PIP_APP_BOUNDS, appBounds);
+ mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP, extra);
+ if (overlay != null) {
+ // Shell transitions might use a root animation leash, which will be removed when
+ // the Recents transition is finished. Launcher attaches the overlay leash to this
+ // animation target leash; thus, we need to reparent it to the actual Task surface now.
+ // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
+ // transition.
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
+ tx.setLayer(overlay, Integer.MAX_VALUE);
+ tx.apply();
+ }
mPipRecentsAnimationListener.onPipAnimationStarted();
}
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ if (newState == PipTransitionState.SWIPING_TO_PIP) {
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ SurfaceControl overlay = extra.getParcelable(
+ SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+ Rect appBounds = extra.getParcelable(
+ SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+
+ Preconditions.checkState(appBounds != null,
+ "App bounds can't be null for " + mPipTransitionState);
+ mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+ } else if (newState == PipTransitionState.ENTERED_PIP) {
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipTransitionState.resetSwipePipToHomeState();
+ }
+ }
+ }
+
//
// IPipAnimationListener Binder proxy helpers
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index b4ca7df10292..72fa3badeb93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -21,21 +21,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
@@ -43,7 +38,6 @@ import com.android.wm.shell.pip.PipTransitionController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.function.Consumer;
/**
* Scheduler for Shell initiated PiP transitions and animations.
@@ -55,31 +49,10 @@ public class PipScheduler {
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
- private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final PipTransitionState mPipTransitionState;
private PipSchedulerReceiver mSchedulerReceiver;
private PipTransitionController mPipTransitionController;
- // pinned PiP task's WC token
- @Nullable
- private WindowContainerToken mPipTaskToken;
-
- // pinned PiP task's leash
- @Nullable
- private SurfaceControl mPinnedTaskLeash;
-
- // true if Launcher has started swipe PiP to home animation
- private boolean mInSwipePipToHomeTransition;
-
- // Overlay leash potentially used during swipe PiP to home transition;
- // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
- @Nullable
- SurfaceControl mSwipePipToHomeOverlay;
-
- // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
- // these are also used to calculate the app icon overlay buffer size.
- @NonNull
- final Rect mSwipePipToHomeAppBounds = new Rect();
-
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -118,11 +91,11 @@ public class PipScheduler {
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
- ShellTaskOrganizer shellTaskOrganizer) {
+ PipTransitionState pipTransitionState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
- mShellTaskOrganizer = shellTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
if (PipUtils.isPip2ExperimentEnabled()) {
// temporary broadcast receiver to initiate exit PiP via expand
@@ -140,25 +113,17 @@ public class PipScheduler {
mPipTransitionController = pipTransitionController;
}
- void setPinnedTaskLeash(SurfaceControl pinnedTaskLeash) {
- mPinnedTaskLeash = pinnedTaskLeash;
- }
-
- void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
- mPipTaskToken = pipTaskToken;
- }
-
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTaskToken == null) {
+ if (mPipTransitionState.mPipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
// final expanded bounds to be inherited from the parent
- wct.setBounds(mPipTaskToken, null);
+ wct.setBounds(mPipTransitionState.mPipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
return wct;
}
@@ -183,43 +148,12 @@ public class PipScheduler {
/**
* Animates resizing of the pinned stack given the duration.
*/
- public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
- if (mPipTaskToken == null) {
+ public void scheduleAnimateResizePip(Rect toBounds) {
+ if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTaskToken, toBounds);
- mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
- }
-
- void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
- mInSwipePipToHomeTransition = true;
- mSwipePipToHomeOverlay = overlay;
- mSwipePipToHomeAppBounds.set(appBounds);
- if (overlay != null) {
- // Shell transitions might use a root animation leash, which will be removed when
- // the Recents transition is finished. Launcher attaches the overlay leash to this
- // animation target leash; thus, we need to reparent it to the actual Task surface now.
- // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
- // transition.
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
- tx.setLayer(overlay, Integer.MAX_VALUE);
- tx.apply();
- }
- }
-
- void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
- mInSwipePipToHomeTransition = inSwipePipToHome;
- }
-
- boolean isInSwipePipToHomeTransition() {
- return mInSwipePipToHomeTransition;
- }
-
- void onExitPip() {
- mPipTaskToken = null;
- mPinnedTaskLeash = null;
+ wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct);
}
}
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 e829d4ef650e..12dce5bf70c0 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
@@ -34,6 +34,7 @@ import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -43,6 +44,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -54,23 +56,33 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.function.Consumer;
-
/**
* Implementation of transitions for PiP on phone.
*/
-public class PipTransition extends PipTransitionController {
+public class PipTransition extends PipTransitionController implements
+ PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = PipTransition.class.getSimpleName();
+ private static final String PIP_TASK_TOKEN = "pip_task_token";
+ private static final String PIP_TASK_LEASH = "pip_task_leash";
+
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
* The fadeout animation is guaranteed to start after the client has drawn under the new config.
*/
private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
+ //
+ // Dependencies
+ //
+
private final Context mContext;
private final PipScheduler mPipScheduler;
- @Nullable
- private WindowContainerToken mPipTaskToken;
+ private final PipTransitionState mPipTransitionState;
+
+ //
+ // Transition tokens
+ //
+
@Nullable
private IBinder mEnterTransition;
@Nullable
@@ -78,7 +90,14 @@ public class PipTransition extends PipTransitionController {
@Nullable
private IBinder mResizeTransition;
- private Consumer<Rect> mFinishResizeCallback;
+ //
+ // Internal state and relevant cached info
+ //
+
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
+ @Nullable
+ private SurfaceControl mPipLeash;
public PipTransition(
Context context,
@@ -88,13 +107,16 @@ public class PipTransition extends PipTransitionController {
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipScheduler pipScheduler) {
+ PipScheduler pipScheduler,
+ PipTransitionState pipTransitionState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
}
@Override
@@ -104,6 +126,10 @@ public class PipTransition extends PipTransitionController {
}
}
+ //
+ // Transition collection stage lifecycle hooks
+ //
+
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
@@ -117,13 +143,11 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void startResizeTransition(WindowContainerTransaction wct,
- Consumer<Rect> onFinishResizeCallback) {
+ public void startResizeTransition(WindowContainerTransaction wct) {
if (wct == null) {
return;
}
mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
- mFinishResizeCallback = onFinishResizeCallback;
}
@Nullable
@@ -146,6 +170,10 @@ public class PipTransition extends PipTransitionController {
}
}
+ //
+ // Transition playing stage lifecycle hooks
+ //
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -163,7 +191,19 @@ public class PipTransition extends PipTransitionController {
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) {
mEnterTransition = null;
- if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition
+ // is being carried out.
+ TransitionInfo.Change pipChange = getPipChange(info);
+
+ // If there is no PiP change, exit this transition handler and potentially try others.
+ if (pipChange == null) return false;
+
+ Bundle extra = new Bundle();
+ extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
+ extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
+
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
// If this is the second transition as a part of swipe PiP to home cuj,
// handle this transition as a special case with no-op animation.
return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
@@ -179,9 +219,11 @@ public class PipTransition extends PipTransitionController {
finishCallback);
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
} else if (transition == mResizeTransition) {
mResizeTransition = null;
+ mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS);
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
@@ -191,6 +233,10 @@ public class PipTransition extends PipTransitionController {
return false;
}
+ //
+ // Animation schedulers and entry points
+ //
+
private boolean startResizeAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -236,11 +282,7 @@ public class PipTransition extends PipTransitionController {
if (pipChange == null) {
return false;
}
- mPipScheduler.setInSwipePipToHomeTransition(false);
- mPipTaskToken = pipChange.getContainer();
-
- // cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
SurfaceControl pipLeash = pipChange.getLeash();
PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
@@ -264,9 +306,9 @@ public class PipTransition extends PipTransitionController {
} else {
final float scaleX = (float) destinationBounds.width() / startBounds.width();
final float scaleY = (float) destinationBounds.height() / startBounds.height();
- final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds);
- SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay;
+ final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
+ mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
+ SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
.setScale(pipLeash, scaleX, scaleY)
@@ -274,7 +316,7 @@ public class PipTransition extends PipTransitionController {
.reparent(overlayLeash, pipLeash)
.setLayer(overlayLeash, Integer.MAX_VALUE);
- if (mPipTaskToken != null) {
+ if (pipTaskToken != null) {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
this::onClientDrawAtTransitionEnd)
@@ -282,7 +324,7 @@ public class PipTransition extends PipTransitionController {
.setPosition(overlayLeash,
(destinationBounds.width() - overlaySize) / 2f,
(destinationBounds.height() - overlaySize) / 2f);
- finishWct.setBoundsChangeTransaction(mPipTaskToken, tx);
+ finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
}
}
startTransaction.apply();
@@ -293,14 +335,6 @@ public class PipTransition extends PipTransitionController {
return true;
}
- private void onClientDrawAtTransitionEnd() {
- startOverlayFadeoutAnimation();
- }
-
- //
- // Subroutines setting up and starting transitions' animations.
- //
-
private void startOverlayFadeoutAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -309,15 +343,17 @@ public class PipTransition extends PipTransitionController {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.remove(mPipScheduler.mSwipePipToHomeOverlay);
+ tx.remove(mPipTransitionState.getSwipePipToHomeOverlay());
tx.apply();
- mPipScheduler.mSwipePipToHomeOverlay = null;
+
+ // We have fully completed enter-PiP animation after the overlay is gone.
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
}
});
animator.addUpdateListener(animation -> {
float alpha = (float) animation.getAnimatedValue();
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply();
+ tx.setAlpha(mPipTransitionState.getSwipePipToHomeOverlay(), alpha).apply();
});
animator.start();
}
@@ -330,10 +366,8 @@ public class PipTransition extends PipTransitionController {
if (pipChange == null) {
return false;
}
- mPipTaskToken = pipChange.getContainer();
-
// cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
startTransaction.apply();
// TODO: b/275910498 Use a new implementation of the PiP animator here.
@@ -349,10 +383,8 @@ public class PipTransition extends PipTransitionController {
if (pipChange == null) {
return false;
}
- mPipTaskToken = pipChange.getContainer();
-
// cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
startTransaction.apply();
finishCallback.onTransitionFinished(null);
@@ -366,7 +398,7 @@ public class PipTransition extends PipTransitionController {
startTransaction.apply();
// TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
- onExitPip();
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
return true;
}
@@ -376,12 +408,20 @@ public class PipTransition extends PipTransitionController {
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
finishCallback.onTransitionFinished(null);
- onExitPip();
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
return true;
}
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {
+ mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+ }
+
//
- // Utility methods for checking PiP-related transition info and requests.
+ // Various helpers to resolve transition requests and infos
//
@Nullable
@@ -442,11 +482,11 @@ public class PipTransition extends PipTransitionController {
}
private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
- if (mPipTaskToken == null) {
+ if (mPipTransitionState.mPipTaskToken == null) {
// PiP removal makes sense if enter-PiP has cached a valid pinned task token.
return false;
}
- TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+ TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
if (pipChange == null) {
// Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
return false;
@@ -460,14 +500,43 @@ public class PipTransition extends PipTransitionController {
return isPipMovedToBack || isPipClosed;
}
- /**
- * TODO: b/275910498 Use a new implementation of the PiP animator here.
- */
- private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
- Rect endBounds, int duration) {}
+ //
+ // Miscellaneous callbacks and listeners
+ //
- private void onExitPip() {
- mPipTaskToken = null;
- mPipScheduler.onExitPip();
+ private void onClientDrawAtTransitionEnd() {
+ if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
+ startOverlayFadeoutAnimation();
+ } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
+ // If we were entering PiP (i.e. playing the animation) with a valid srcRectHint,
+ // and then we get a signal on client finishing its draw after the transition
+ // has ended, then we have fully entered PiP.
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ }
+ }
+
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.ENTERING_PIP:
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ mPipTransitionState.mPipTaskToken = extra.getParcelable(
+ PIP_TASK_TOKEN, WindowContainerToken.class);
+ mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
+ PIP_TASK_LEASH, SurfaceControl.class);
+ boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
+ && mPipTransitionState.mPinnedTaskLeash != null;
+
+ Preconditions.checkState(hasValidTokenAndLeash,
+ "Unexpected bundle for " + mPipTransitionState);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ mPipTransitionState.mPipTaskToken = null;
+ mPipTransitionState.mPinnedTaskLeash = null;
+ break;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
new file mode 100644
index 000000000000..f7bc622b6195
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -0,0 +1,275 @@
+/*
+ * 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.phone;
+
+import android.annotation.IntDef;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains the state relevant to carry out or probe the status of PiP transitions.
+ *
+ * <p>Existing and new PiP components can subscribe to PiP transition related state changes
+ * via <code>PipTransitionStateChangedListener</code>.</p>
+ *
+ * <p><code>PipTransitionState</code> users shouldn't rely on listener execution ordering.
+ * For example, if a class <code>Foo</code> wants to change some arbitrary state A that belongs
+ * to some other class <code>Bar</code>, a special care must be given when manipulating state A in
+ * <code>Foo#onPipTransitionStateChanged()</code>, since that's the responsibility of
+ * the class <code>Bar</code>.</p>
+ *
+ * <p>Hence, the recommended usage for classes who want to subscribe to
+ * <code>PipTransitionState</code> changes is to manipulate only their own internal state or
+ * <code>PipTransitionState</code> state.</p>
+ *
+ * <p>If there is some state that must be manipulated in another class <code>Bar</code>, it should
+ * just be moved to <code>PipTransitionState</code> and become a shared state
+ * between Foo and Bar.</p>
+ *
+ * <p>Moreover, <code>onPipTransitionStateChanged(oldState, newState, extra)</code>
+ * receives a <code>Bundle</code> extra object that can be optionally set via
+ * <code>setState(state, extra)</code>. This can be used to resolve extra information to update
+ * relevant internal or <code>PipTransitionState</code> state. However, each listener
+ * needs to check for whether the extra passed is correct for a particular state,
+ * and throw an <code>IllegalStateException</code> otherwise.</p>
+ */
+public class PipTransitionState {
+ public static final int UNDEFINED = 0;
+
+ // State for Launcher animating the swipe PiP to home animation.
+ public static final int SWIPING_TO_PIP = 1;
+
+ // State for Shell animating enter PiP or jump-cutting to PiP mode after Launcher animation.
+ public static final int ENTERING_PIP = 2;
+
+ // State for app finishing drawing in PiP mode as a final step in enter PiP flow.
+ public static final int ENTERED_PIP = 3;
+
+ // State for scheduling a transition to change PiP bounds.
+ public static final int CHANGING_PIP_BOUNDS = 4;
+
+ // State for app potentially finishing drawing in new PiP bounds after resize is complete.
+ public static final int CHANGED_PIP_BOUNDS = 5;
+
+ // State for starting exiting PiP.
+ public static final int EXITING_PIP = 6;
+
+ // State for finishing exit PiP flow.
+ public static final int EXITED_PIP = 7;
+
+ private static final int FIRST_CUSTOM_STATE = 1000;
+
+ private int mPrevCustomState = FIRST_CUSTOM_STATE;
+
+ @IntDef(prefix = { "TRANSITION_STATE_" }, value = {
+ UNDEFINED,
+ SWIPING_TO_PIP,
+ ENTERING_PIP,
+ ENTERED_PIP,
+ CHANGING_PIP_BOUNDS,
+ CHANGED_PIP_BOUNDS,
+ EXITING_PIP,
+ EXITED_PIP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransitionState {}
+
+ @TransitionState
+ private int mState;
+
+ //
+ // Swipe up to enter PiP related state
+ //
+
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
+ // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
+ // these are also used to calculate the app icon overlay buffer size.
+ @NonNull
+ private final Rect mSwipePipToHomeAppBounds = new Rect();
+
+ //
+ // Tokens and leashes
+ //
+
+ // pinned PiP task's WC token
+ @Nullable
+ WindowContainerToken mPipTaskToken;
+
+ // pinned PiP task's leash
+ @Nullable
+ SurfaceControl mPinnedTaskLeash;
+
+ // Overlay leash potentially used during swipe PiP to home transition;
+ // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
+ @Nullable
+ private SurfaceControl mSwipePipToHomeOverlay;
+
+ /**
+ * An interface to track state updates as we progress through PiP transitions.
+ */
+ public interface PipTransitionStateChangedListener {
+
+ /** Reports changes in PiP transition state. */
+ void onPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState, @Nullable Bundle extra);
+ }
+
+ private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
+
+ /**
+ * @return the state of PiP in the context of transitions.
+ */
+ @TransitionState
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Sets the state of PiP in the context of transitions.
+ */
+ public void setState(@TransitionState int state) {
+ setState(state, null /* extra */);
+ }
+
+ /**
+ * Sets the state of PiP in the context of transitions
+ *
+ * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
+ */
+ public void setState(@TransitionState int state, @Nullable Bundle extra) {
+ if (state == ENTERING_PIP || state == SWIPING_TO_PIP) {
+ // Whenever we are entering PiP caller must provide extra state to set as well.
+ Preconditions.checkArgument(extra != null && !extra.isEmpty(),
+ "No extra bundle for either ENTERING_PIP or SWIPING_TO_PIP state.");
+ }
+ if (mState != state) {
+ dispatchPipTransitionStateChanged(mState, state, extra);
+ mState = state;
+ }
+ }
+
+ private void dispatchPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState, @Nullable Bundle extra) {
+ mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra));
+ }
+
+ /**
+ * Adds a {@link PipTransitionStateChangedListener} for future PiP transition state updates.
+ */
+ public void addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener) {
+ if (mCallbacks.contains(listener)) {
+ return;
+ }
+ mCallbacks.add(listener);
+ }
+
+ /**
+ * @return true if provided {@link PipTransitionStateChangedListener}
+ * is registered before removing it.
+ */
+ public boolean removePipTransitionStateChangedListener(
+ PipTransitionStateChangedListener listener) {
+ return mCallbacks.remove(listener);
+ }
+
+ /**
+ * @return true if we have fully entered PiP.
+ */
+ public boolean isInPip() {
+ return mState > ENTERING_PIP && mState < EXITING_PIP;
+ }
+
+ void setSwipePipToHomeState(@Nullable SurfaceControl overlayLeash,
+ @NonNull Rect appBounds) {
+ mInSwipePipToHomeTransition = true;
+ if (overlayLeash != null && !appBounds.isEmpty()) {
+ mSwipePipToHomeOverlay = overlayLeash;
+ mSwipePipToHomeAppBounds.set(appBounds);
+ }
+ }
+
+ void resetSwipePipToHomeState() {
+ mInSwipePipToHomeTransition = false;
+ mSwipePipToHomeOverlay = null;
+ mSwipePipToHomeAppBounds.setEmpty();
+ }
+
+ /**
+ * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too.
+ */
+ public boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
+ /**
+ * @return the overlay used during swipe PiP to home for invalid srcRectHints in auto-enter PiP;
+ * null if srcRectHint provided is valid.
+ */
+ @Nullable
+ public SurfaceControl getSwipePipToHomeOverlay() {
+ return mSwipePipToHomeOverlay;
+ }
+
+ /**
+ * @return app bounds used to calculate
+ */
+ @NonNull
+ public Rect getSwipePipToHomeAppBounds() {
+ return mSwipePipToHomeAppBounds;
+ }
+
+ /**
+ * @return a custom state solely for internal use by the caller.
+ */
+ @TransitionState
+ public int getCustomState() {
+ return ++mPrevCustomState;
+ }
+
+ private String stateToString() {
+ switch (mState) {
+ case UNDEFINED: return "undefined";
+ case ENTERING_PIP: return "entering-pip";
+ case ENTERED_PIP: return "entered-pip";
+ case CHANGING_PIP_BOUNDS: return "changing-bounds";
+ case CHANGED_PIP_BOUNDS: return "changed-bounds";
+ case EXITING_PIP: return "exiting-pip";
+ case EXITED_PIP: return "exited-pip";
+ }
+ throw new IllegalStateException("Unknown state: " + mState);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
+ stateToString(), mInSwipePipToHomeTransition);
+ }
+}
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 2a50b191b1e9..5e9451a09d41 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
@@ -201,7 +201,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
private final TransactionPool mTransactionPool;
- private final SplitScreenTransitions mSplitTransitions;
+ private SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
private final ShellExecutor mMainExecutor;
// Cache live tile tasks while entering recents, evict them from stages in finish transaction
@@ -399,6 +399,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mSplitTransitions;
}
+ @VisibleForTesting
+ void setSplitTransitions(SplitScreenTransitions splitScreenTransitions) {
+ mSplitTransitions = splitScreenTransitions;
+ }
+
public boolean isSplitScreenVisible() {
return mSideStageListener.mVisible && mMainStageListener.mVisible;
}
@@ -583,7 +588,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
- if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+ if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
mTaskOrganizer.applyTransaction(wct);
return;
}
@@ -622,7 +627,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
- if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
+ if (mMixedHandler != null && mMixedHandler.isIntentInPip(intent)) {
mTaskOrganizer.applyTransaction(wct);
return;
}
@@ -711,16 +716,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
- if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- }
- if (mRecentTasks.isPresent()) {
- mRecentTasks.get().removeSplitPair(taskId1);
- }
- options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, null);
- wct.startTask(taskId1, options1);
- mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ startSingleTask(taskId1, options1, wct, remoteTransition);
return;
}
@@ -741,11 +737,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
"startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (taskId == INVALID_TASK_ID) {
- options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, null);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent);
+ boolean secondTaskPipped = mMixedHandler.isTaskInPip(taskId, mTaskOrganizer);
+ if (taskId == INVALID_TASK_ID || secondTaskPipped) {
+ startSingleIntent(pendingIntent, fillInIntent, options1, wct, remoteTransition);
+ return;
+ }
+
+ if (firstIntentPipped) {
+ startSingleTask(taskId, options2, wct, remoteTransition);
return;
}
@@ -757,6 +757,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
}
+ /**
+ * @param taskId Starts this task in fullscreen, removing it from existing pairs if it was part
+ * of one.
+ */
+ private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct,
+ RemoteTransition remoteTransition) {
+ if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) {
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ if (mRecentTasks.isPresent()) {
+ mRecentTasks.get().removeSplitPair(taskId);
+ }
+ options = options != null ? options : new Bundle();
+ addActivityOptions(options, null);
+ wct.startTask(taskId, options);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ }
+
/** Starts a shortcut and a task to a split pair in one transition. */
void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -844,6 +862,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
+ boolean handledForPipSplitLaunch = handlePippedSplitIntentsLaunch(
+ pendingIntent1,
+ pendingIntent2,
+ options1,
+ options2,
+ shortcutInfo1,
+ shortcutInfo2,
+ wct,
+ fillInIntent1,
+ fillInIntent2,
+ remoteTransition);
+ if (handledForPipSplitLaunch) {
+ return;
+ }
+
if (!mMainStage.isActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
@@ -878,6 +911,46 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setEnterInstanceId(instanceId);
}
+ /**
+ * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
+ * launch the non-pipped app as a fullscreen app, otherwise no-op.
+ */
+ private boolean handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2, Bundle options1, Bundle options2,
+ ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct,
+ Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition) {
+ // If one of the split apps to start is in Pip, only launch the non-pip app in fullscreen
+ boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent1);
+ boolean secondIntentPipped = mMixedHandler.isIntentInPip(pendingIntent2);
+ if (firstIntentPipped || secondIntentPipped) {
+ Bundle options = secondIntentPipped ? options1 : options2;
+ options = options == null ? new Bundle() : options;
+ addActivityOptions(options, null);
+ if (shortcutInfo1 != null || shortcutInfo2 != null) {
+ ShortcutInfo infoToLaunch = secondIntentPipped ? shortcutInfo1 : shortcutInfo2;
+ wct.startShortcut(mContext.getPackageName(), infoToLaunch, options);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ } else {
+ PendingIntent intentToLaunch = secondIntentPipped ? pendingIntent1 : pendingIntent2;
+ Intent fillInIntentToLaunch = secondIntentPipped ? fillInIntent1 : fillInIntent2;
+ startSingleIntent(intentToLaunch, fillInIntentToLaunch, options, wct,
+ remoteTransition);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /** @param pendingIntent Starts this intent in fullscreen */
+ private void startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options,
+ WindowContainerTransaction wct,
+ RemoteTransition remoteTransition) {
+ Bundle optionsToLaunch = options != null ? options : new Bundle();
+ addActivityOptions(optionsToLaunch, null);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, optionsToLaunch);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ }
+
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4bc0dc00175b..4d02ec26e18e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -563,22 +563,23 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
/** Use to when split use intent to enter, check if this enter transition should be mixed or
* not.*/
- public boolean shouldSplitEnterMixed(PendingIntent intent) {
+ public boolean isIntentInPip(PendingIntent intent) {
// Check if this intent package is same as pip one or not, if true we want let the pip
// task enter split.
if (mPipHandler != null) {
- return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
+ return mPipHandler
+ .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
}
return false;
}
/** Use to when split use taskId to enter, check if this enter transition should be mixed or
* not.*/
- public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+ public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
// Check if this intent package is same as pip one or not, if true we want let the pip
// task enter split.
if (mPipHandler != null) {
- return mPipHandler.isInPipPackage(
+ return mPipHandler.isPackageActiveInPip(
SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
}
return false;
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index 75dfeba3e662..17cace0da739 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -23,6 +23,7 @@ import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
import android.tools.flicker.assertors.assertions.LauncherWindowMovesToTop
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
@@ -114,5 +115,30 @@ class DesktopModeFlickerScenarios {
)
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val CORNER_RESIZE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("CORNER_RESIZE"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.CHANGE
+ }
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt
new file mode 100644
index 000000000000..8d1a53021683
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithCornerResizeLandscape : ResizeAppWithCornerResize(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["CORNER_RESIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt
new file mode 100644
index 000000000000..2d81c8c44799
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithCornerResizePortrait : ResizeAppWithCornerResize(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["CORNER_RESIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
new file mode 100644
index 000000000000..289ca9f5d92e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
@@ -0,0 +1,68 @@
+/*
+ * 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
+
+
+@Ignore("Base Test Class")
+abstract class ResizeAppWithCornerResize
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ 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(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun resizeAppWithCornerResize() {
+ testApp.cornerResize(wmHelper, device, DesktopModeAppHelper.Corners.RIGHT_TOP, 50, -50)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
new file mode 100644
index 000000000000..bd8ac379b86f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.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.wm.shell.pip2;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.testing.AndroidTestingRunner;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit test against {@link PhoneSizeSpecSource}.
+ *
+ * This test mocks the PiP2 flag to be true.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipTransitionStateTest extends ShellTestCase {
+ private static final String EXTRA_ENTRY_KEY = "extra_entry_key";
+ private PipTransitionState mPipTransitionState;
+ private PipTransitionState.PipTransitionStateChangedListener mStateChangedListener;
+ private Parcelable mEmptyParcelable;
+
+ @Before
+ public void setUp() {
+ mPipTransitionState = new PipTransitionState();
+ mPipTransitionState.setState(PipTransitionState.UNDEFINED);
+ mEmptyParcelable = new Bundle();
+ }
+
+ @Test
+ public void testEnteredState_withoutExtra() {
+ mStateChangedListener = (oldState, newState, extra) -> {
+ Assert.assertEquals(PipTransitionState.ENTERED_PIP, newState);
+ Assert.assertNull(extra);
+ };
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+
+ @Test
+ public void testEnteredState_withExtra() {
+ mStateChangedListener = (oldState, newState, extra) -> {
+ Assert.assertEquals(PipTransitionState.ENTERED_PIP, newState);
+ Assert.assertNotNull(extra);
+ Assert.assertEquals(mEmptyParcelable, extra.getParcelable(EXTRA_ENTRY_KEY));
+ };
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP, extra);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEnteringState_withoutExtra() {
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSwipingToPipState_withoutExtra() {
+ mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP);
+ }
+
+ @Test
+ public void testCustomState_withExtra_thenEntered_withoutExtra() {
+ final int customState = mPipTransitionState.getCustomState();
+ mStateChangedListener = (oldState, newState, extra) -> {
+ if (newState == customState) {
+ Assert.assertNotNull(extra);
+ Assert.assertEquals(mEmptyParcelable, extra.getParcelable(EXTRA_ENTRY_KEY));
+ return;
+ } else if (newState == PipTransitionState.ENTERED_PIP) {
+ Assert.assertNull(extra);
+ return;
+ }
+ Assert.fail("Neither custom not ENTERED_PIP state is received.");
+ };
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(customState, extra);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+}
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 d819261ecba2..d7c383523a6f 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
@@ -40,10 +40,12 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -51,6 +53,7 @@ import android.os.Handler;
import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.window.RemoteTransition;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -74,6 +77,7 @@ import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
@@ -111,6 +115,8 @@ public class StageCoordinatorTests extends ShellTestCase {
private TransactionPool mTransactionPool;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
+ @Mock
+ private DefaultMixedHandler mDefaultMixedHandler;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -370,6 +376,96 @@ public class StageCoordinatorTests extends ShellTestCase {
}
}
+ @Test
+ public void testSplitIntentAndTaskWithPippedApp_launchFullscreen() {
+ int taskId = 9;
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+ PendingIntent pendingIntent = mock(PendingIntent.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ when(remoteTransition.getDebugName()).thenReturn("");
+ // Test launching second task full screen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+ mStageCoordinator.startIntentAndTask(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*option1*/,
+ taskId,
+ null /*option2*/,
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(1))
+ .startFullscreenTransition(any(), any());
+
+ // Test launching first intent fullscreen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+ when(mDefaultMixedHandler.isTaskInPip(taskId, mTaskOrganizer)).thenReturn(true);
+ mStageCoordinator.startIntentAndTask(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*option1*/,
+ taskId,
+ null /*option2*/,
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(2))
+ .startFullscreenTransition(any(), any());
+ }
+
+ @Test
+ public void testSplitIntentsWithPippedApp_launchFullscreen() {
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+ PendingIntent pendingIntent = mock(PendingIntent.class);
+ PendingIntent pendingIntent2 = mock(PendingIntent.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ when(remoteTransition.getDebugName()).thenReturn("");
+ // Test launching second task full screen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+ mStageCoordinator.startIntents(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ pendingIntent2,
+ null /*fillInIntent2*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(1))
+ .startFullscreenTransition(any(), any());
+
+ // Test launching first intent fullscreen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent2)).thenReturn(true);
+ mStageCoordinator.startIntents(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ pendingIntent2,
+ null /*fillInIntent2*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(2))
+ .startFullscreenTransition(any(), any());
+ }
+
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 1fcb6920db14..cfca48084d97 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -34,7 +34,9 @@ namespace flags = com::android::graphics::hwui::flags;
namespace android {
-inline constexpr int kHighContrastTextBorderWidth = 4;
+// These should match the constants in framework/base/core/java/android/text/Layout.java
+inline constexpr float kHighContrastTextBorderWidth = 4.0f;
+inline constexpr float kHighContrastTextBorderWidthFactor = 0.2f;
static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
const Paint& paint, Canvas* canvas) {
@@ -48,7 +50,16 @@ static void simplifyPaint(int color, Paint* paint) {
paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
- paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize());
+
+ if (flags::high_contrast_text_small_text_rect()) {
+ paint->setStrokeWidth(
+ std::max(kHighContrastTextBorderWidth,
+ kHighContrastTextBorderWidthFactor * paint->getSkFont().getSize()));
+ } else {
+ auto borderWidthFactor = 0.04f;
+ paint->setStrokeWidth(kHighContrastTextBorderWidth +
+ borderWidthFactor * paint->getSkFont().getSize());
+ }
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
}
@@ -106,36 +117,7 @@ public:
Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- if (flags::high_contrast_text_small_text_rect()) {
- const SkFont& font = paint.getSkFont();
- auto padding = kHighContrastTextBorderWidth + 0.1f * font.getSize();
-
- // Draw the background only behind each glyph's bounds. We do this instead of using
- // the bounds of the entire layout, because the layout includes alignment whitespace
- // etc which can obscure other text from separate passes (e.g. emojis).
- // Merge all the glyph bounds into one rect for this line, since drawing a rect for
- // each glyph is expensive.
- SkRect glyphBounds;
- SkRect bgBounds;
- for (size_t i = start; i < end; i++) {
- auto glyph = layout.getGlyphId(i);
-
- font.getBounds(reinterpret_cast<const SkGlyphID*>(&glyph), 1, &glyphBounds,
- &paint);
- glyphBounds.offset(layout.getX(i), layout.getY(i));
-
- bgBounds.join(glyphBounds);
- }
-
- if (!bgBounds.isEmpty()) {
- bgBounds.offset(x, y);
- bgBounds.outset(padding, padding);
- canvas->drawRect(bgBounds.fLeft, bgBounds.fTop, bgBounds.fRight,
- bgBounds.fBottom, outlinePaint);
- }
- } else {
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
- }
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
// inner
gDrawTextBlobMode = DrawTextBlobMode::HctInner;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 64dcf6e8f573..395354ef8f20 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -34,6 +34,7 @@ android_test {
"compatibility-device-util-axt",
"platform-test-annotations",
"truth",
+ "uiautomator-helpers",
],
srcs: [
"src/**/*.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0ab99fac9ba3..66943d453873 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -30,6 +30,8 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.KeyguardManager;
@@ -43,6 +45,8 @@ import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
@@ -51,6 +55,8 @@ import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Configurator;
+import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
@@ -60,7 +66,6 @@ import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,11 +81,13 @@ public class AccessibilityMenuServiceTest {
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
private static final int TIMEOUT_UI_CHANGE_S = 5;
private static final int NO_GLOBAL_ACTION = -1;
- private static final Intent INTENT_OPEN_MENU = new Intent(INTENT_TOGGLE_MENU)
- .setPackage(PACKAGE_NAME);
+ private static final Intent INTENT_OPEN_MENU =
+ new Intent(INTENT_TOGGLE_MENU).setPackage(PACKAGE_NAME);
+ private static final String SERVICE_NAME = PACKAGE_NAME + "/.AccessibilityMenuService";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
+ private static UiDevice sUiDevice;
private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
@@ -91,12 +98,14 @@ public class AccessibilityMenuServiceTest {
@BeforeClass
public static void classSetup() throws Throwable {
- final String serviceName = PACKAGE_NAME + "/.AccessibilityMenuService";
+ Configurator.getInstance()
+ .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sUiAutomation.adoptShellPermissionIdentity(
UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
+ sUiDevice = UiDevice.getInstance(sInstrumentation);
final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -117,13 +126,13 @@ public class AccessibilityMenuServiceTest {
// Enable a11yMenu service.
Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, serviceName);
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
TestUtils.waitUntil("Failed to enable service",
TIMEOUT_SERVICE_STATUS_CHANGE_S,
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
- info -> info.getId().contains(serviceName)).count() == 1);
+ info -> info.getId().contains(SERVICE_NAME)).count() == 1);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -159,8 +168,11 @@ public class AccessibilityMenuServiceTest {
public void tearDown() throws Throwable {
closeMenu();
sLastGlobalAction.set(NO_GLOBAL_ACTION);
+ // Leave the device in clean state when the test finished
+ unlockSignal();
// dismisses screenshot popup if present.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ sUiDevice.pressBack();
+ sUiDevice.pressHome();
}
private static boolean isMenuVisible() {
@@ -168,38 +180,25 @@ public class AccessibilityMenuServiceTest {
return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
}
- private static void wakeUpScreen() throws Throwable {
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- TestUtils.waitUntil("Screen did not wake up.",
- TIMEOUT_UI_CHANGE_S,
- () -> sPowerManager.isInteractive());
+ private static void wakeUpScreen() throws RemoteException {
+ sUiDevice.wakeUp();
+ WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
+ assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
}
private static void closeScreen() throws Throwable {
- Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
- TestUtils.waitUntil("Screen did not close.",
- TIMEOUT_UI_CHANGE_S,
- () -> !sPowerManager.isInteractive()
- && display.getState() == Display.STATE_OFF
- );
+ WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
+ assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
}
private static void openMenu() throws Throwable {
unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-
- TestUtils.waitUntil("Timed out before menu could appear.",
- TIMEOUT_UI_CHANGE_S,
- () -> {
- if (isMenuVisible()) {
- return true;
- } else {
- unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return false;
- }
- });
+ if (!isMenuVisible()) {
+ sInstrumentation.getTargetContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
+ WaitUtils.ensureThat("Accessibility Menu is visible", () -> isMenuVisible());
+ }
}
private static void closeMenu() throws Throwable {
@@ -342,7 +341,9 @@ public class AccessibilityMenuServiceTest {
sUiAutomation.executeAndWaitForEvent(
() -> assistantButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -358,7 +359,9 @@ public class AccessibilityMenuServiceTest {
sUiAutomation.executeAndWaitForEvent(
() -> settingsButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -454,24 +457,40 @@ public class AccessibilityMenuServiceTest {
}
@Test
- @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
- + "Coverage is low-impact.")
public void testOnScreenLock_cannotOpenMenu() throws Throwable {
closeScreen();
wakeUpScreen();
+ sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
TestUtils.waitUntil("Did not receive signal that menu cannot open",
TIMEOUT_UI_CHANGE_S,
- () -> {
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return sOpenBlocked.get();
- });
+ sOpenBlocked::get);
+ }
+
+ private static void unlockSignal() throws RemoteException {
+ if (!sKeyguardManager.isKeyguardLocked()) {
+ return;
+ }
+ // go/adb-cheats#unlock-screen
+ wakeUpScreen();
+ if (sKeyguardManager.isKeyguardLocked()) {
+ sUiDevice.pressMenu();
+ }
+ WaitUtils.ensureThat(
+ "Device unlocked & isInteractive",
+ () -> isScreenOn() && !sKeyguardManager.isKeyguardLocked());
+ }
+
+ private static boolean isScreenOn() {
+ int display = Display.DEFAULT_DISPLAY;
+ return sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_ON;
}
- private static void unlockSignal() {
- // MENU unlocks screen,
- // BACK closes any menu that may appear if the screen wasn't locked.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ private static boolean isScreenOff() {
+ int display = Display.DEFAULT_DISPLAY;
+ return !sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_OFF;
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index e07cd0539a46..ec3c0030c44f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -27,6 +27,7 @@ import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.res.R
@@ -79,6 +80,7 @@ fun CommunalContainer(
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
+ val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -128,6 +130,10 @@ fun CommunalContainer(
CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
+
+ // Touches on the notification shade in blank areas fall through to the glanceable hub. When the
+ // shade is showing, we block all touches in order to prevent this unwanted behavior.
+ Box(modifier = Modifier.fillMaxSize().allowGestures(touchesAllowed))
}
/** Scene containing the glanceable hub UI. */
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 338987a60227..dff9b3bae481 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
@@ -20,6 +20,8 @@ import android.appwidget.AppWidgetHostView
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
@@ -103,6 +105,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
@@ -115,8 +118,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -300,7 +301,7 @@ fun CommunalHub(
viewModel.onHidePopup()
viewModel.onOpenWidgetEditor(selectedKey.value)
},
- onHide = { viewModel.onHidePopup()}
+ onHide = { viewModel.onHidePopup() }
)
}
null -> {}
@@ -374,7 +375,7 @@ private fun ScrollOnUpdatedLiveContentEffect(
liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
// Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -841,17 +842,31 @@ private fun WidgetContent(
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ val context = LocalContext.current
+ val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+ val accessibilityLabel =
+ remember(model, context) {
+ model.providerInfo.loadLabel(context.packageManager).toString().trim()
+ }
+ val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
Box(
modifier =
- modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
- Modifier.pointerInput(Unit) {
- // consume tap to prevent the child view from triggering interactions with the
- // app widget
- observeTaps(shouldConsume = true) { _ ->
- viewModel.onOpenEnableWorkProfileDialog()
+ modifier
+ .thenIf(!viewModel.isEditMode && model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ // consume tap to prevent the child view from triggering interactions with
+ // the app widget
+ observeTaps(shouldConsume = true) { _ ->
+ viewModel.onOpenEnableWorkProfileDialog()
+ }
+ }
+ }
+ .thenIf(viewModel.isEditMode) {
+ Modifier.semantics {
+ contentDescription = accessibilityLabel
+ onClick(label = clickActionLabel, action = null)
}
}
- }
) {
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
@@ -863,8 +878,19 @@ private fun WidgetContent(
// Remove the extra padding applied to AppWidgetHostView to allow widgets to
// occupy the entire box.
setPadding(0)
+ accessibilityDelegate = viewModel.widgetAccessibilityDelegate
}
},
+ update = {
+ it.apply {
+ importantForAccessibility =
+ if (isFocusable) {
+ IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ } else {
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+ }
+ },
// For reusing composition in lazy lists.
onReset = {},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index e7bfee34e76a..33d2cc40cb06 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -40,6 +40,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
@@ -274,6 +275,9 @@ fun LazyGridItemScope.DraggableItem(
AnimatedVisibility(
modifier =
Modifier.matchParentSize()
+ // Avoid taking focus away from the content when using explore-by-touch with
+ // accessibility tools.
+ .clearAndSetSemantics {}
// Do not consume motion events in the highlighted item and pass them down to
// the content.
.pointerInteropFilter { false },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index f354b80692f5..74af3ca19266 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -98,10 +98,10 @@ private class ClickableSliceView(
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
- return onClick != null || super.onInterceptTouchEvent(ev)
+ return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
}
override fun onClick(v: View?) {
- onClick?.let { it() } ?: super.onClick(v)
+ onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 4f3a6c84b13d..874c0a299d2b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -38,6 +38,8 @@ import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -79,6 +81,12 @@ class ToggleButtonComponent(
modifier =
Modifier.fillMaxSize().padding(8.dp).semantics {
role = Role.Switch
+ toggleableState =
+ if (viewModel.isActive) {
+ ToggleableState.On
+ } else {
+ ToggleableState.Off
+ }
contentDescription = label
},
onClick = { onCheckedChange(!viewModel.isActive) },
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 4273b4fbf7b8..ca643231e874 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -217,18 +217,18 @@ internal class ElementNode(
maybePruneMaps(layoutImpl, prevElement, prevSceneState)
}
- override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
// TODO(b/324191441): Investigate whether making this check more complex (checking if this
// element is shared or transformed) would lead to better performance.
- return layoutImpl.state.currentTransitions.isEmpty()
+ return layoutImpl.state.isTransitioning()
}
- override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ override fun Placeable.PlacementScope.isPlacementApproachInProgress(
lookaheadCoordinates: LayoutCoordinates
): Boolean {
// TODO(b/324191441): Investigate whether making this check more complex (checking if this
// element is shared or transformed) would lead to better performance.
- return layoutImpl.state.currentTransitions.isEmpty()
+ return layoutImpl.state.isTransitioning()
}
@ExperimentalComposeUiApi
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 7fb5a4d0cc27..339868c9fbc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -26,7 +26,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -74,7 +74,9 @@ internal class Scene(
Box(
modifier
.zIndex(zIndex)
- .intermediateLayout { measurable, constraints ->
+ .approachLayout(
+ isMeasurementApproachInProgress = { scope.layoutState.isTransitioning() }
+ ) { measurable, constraints ->
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
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 ad691ba54607..d383cec324d3 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
@@ -251,8 +251,8 @@ private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutIm
private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
Modifier.Node(), ApproachLayoutModifierNode {
- override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
- return layoutImpl.state.currentTransition == null
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
+ return layoutImpl.state.isTransitioning()
}
@ExperimentalComposeUiApi
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
index bd36cb8655ac..b392c679e49c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
@@ -18,15 +18,17 @@ package com.android.compose.animation.scene.modifiers
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.unit.Constraints
import com.android.compose.animation.scene.SceneTransitionLayoutState
@OptIn(ExperimentalComposeUiApi::class)
internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier {
- return intermediateLayout { measurable, constraints ->
+ return approachLayout(isMeasurementApproachInProgress = { layoutState.isTransitioning() }) {
+ measurable,
+ constraints ->
if (layoutState.currentTransition == null) {
- return@intermediateLayout measurable.measure(constraints).run {
+ return@approachLayout measurable.measure(constraints).run {
layout(width, height) { place(0, 0) }
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index b1d7055573b6..92e1b2cd030c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -46,7 +46,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -91,7 +91,9 @@ class ElementTest {
modifier
.offset(offset)
.element(key)
- .intermediateLayout { measurable, constraints ->
+ .approachLayout(
+ isMeasurementApproachInProgress = { layoutState.isTransitioning() }
+ ) { measurable, constraints ->
onLayout()
val placement = measurable.measure(constraints)
layout(placement.width, placement.height) {
@@ -525,7 +527,7 @@ class ElementTest {
// page should be composed.
HorizontalPager(
pagerState,
- outOfBoundsPageCount = 0,
+ beyondViewportPageCount = 0,
) { page ->
when (page) {
0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index ac7717b41a5a..ce4c52757e78 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -202,11 +202,11 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) {
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun data(): List<TestCase> =
- listOf(
- TestCase(NestedScrollSource.Drag),
- TestCase(NestedScrollSource.Fling),
- TestCase(NestedScrollSource.Wheel),
+ fun data(): List<TestCase> {
+ return listOf(
+ TestCase(NestedScrollSource.UserInput),
+ TestCase(NestedScrollSource.SideEffect),
)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index c96a8ce9e159..569116c6124a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -24,17 +24,21 @@ import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -45,8 +49,14 @@ import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -63,6 +73,7 @@ import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -94,6 +105,8 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var shadeTestUtil: ShadeTestUtil
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var communalRepository: FakeCommunalRepository
private lateinit var underTest: CommunalViewModel
@@ -106,12 +119,14 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
keyguardRepository = kosmos.fakeKeyguardRepository
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
tutorialRepository = kosmos.fakeCommunalTutorialRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
shadeTestUtil = kosmos.shadeTestUtil
+ communalRepository = kosmos.fakeCommunalRepository
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -125,6 +140,8 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
underTest =
CommunalViewModel(
testScope,
+ context.resources,
+ kosmos.keyguardTransitionInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -326,6 +343,129 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
assertThat(underTest.canChangeScene()).isFalse()
}
+ @Test
+ fun touchesAllowed_shadeNotExpanded() =
+ testScope.runTest {
+ val touchesAllowed by collectLastValue(underTest.touchesAllowed)
+
+ // On keyguard without any shade expansion.
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ runCurrent()
+ assertThat(touchesAllowed).isTrue()
+ }
+
+ @Test
+ fun touchesAllowed_shadeExpanded() =
+ testScope.runTest {
+ val touchesAllowed by collectLastValue(underTest.touchesAllowed)
+
+ // On keyguard with shade fully expanded.
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setLockscreenShadeExpansion(1f)
+ runCurrent()
+ assertThat(touchesAllowed).isFalse()
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Open bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+
+ // Transitioned to bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenNotOnCommunalScene() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // Transitioned away from communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank))
+ )
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+
+ assertThat(isFocusable).isEqualTo(true)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenQsIsExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Qs is expanded.
+ shadeTestUtil.setQsExpansion(1f)
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
whenever(user.isMain).thenReturn(isMainUser)
userRepository.setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index d7023307b0b9..360f284f3072 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -33,6 +33,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -46,6 +47,7 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
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.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -317,6 +319,51 @@ class FromAodTransitionInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetectedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Start going to AOD on first button push
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ value = 0f
+ ),
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f
+ ),
+ ),
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(100) // account for debouncing
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
fun testTransitionToLockscreen_onWakeUpFromAod_dismissibleKeyguard_securitySwipe() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 0a29821c0660..3b6f6a2d5e1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 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.
@@ -12,20 +12,19 @@
* 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.keyguard.domain.interactor
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +32,10 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
@@ -50,15 +52,18 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
- val configRepository = kosmos.fakeConfigurationRepository
val keyguardRepository = kosmos.fakeKeyguardRepository
+ val shadeRepository = kosmos.fakeShadeRepository
+ val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val burnInProgress = 1f
private val burnInYOffset = 20
@@ -67,7 +72,6 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() {
private lateinit var bouncerRepository: KeyguardBouncerRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var burnInInteractor: BurnInInteractor
- private lateinit var shadeRepository: FakeShadeRepository
private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,11 +79,22 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() {
private lateinit var underTest: UdfpsKeyguardInteractor
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
bouncerRepository = FakeKeyguardBouncerRepository()
- shadeRepository = FakeShadeRepository()
fakeCommandQueue = FakeCommandQueue()
burnInInteractor =
BurnInInteractor(
@@ -93,10 +108,10 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() {
underTest =
UdfpsKeyguardInteractor(
- configRepository,
burnInInteractor,
kosmos.keyguardInteractor,
- shadeRepository,
+ kosmos.shadeInteractor,
+ kosmos.shadeLockscreenInteractor,
dialogManager,
)
}
@@ -183,13 +198,13 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() {
val qsProgress by collectLastValue(underTest.qsProgress)
assertThat(qsProgress).isEqualTo(0f)
- shadeRepository.setQsExpansion(.22f)
+ shadeTestUtil.setQsExpansion(.22f)
assertThat(qsProgress).isEqualTo(.44f)
- shadeRepository.setQsExpansion(.5f)
+ shadeTestUtil.setQsExpansion(.5f)
assertThat(qsProgress).isEqualTo(1f)
- shadeRepository.setQsExpansion(.7f)
+ shadeTestUtil.setQsExpansion(.7f)
assertThat(qsProgress).isEqualTo(1f)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index e3eca6729188..030aa243db89 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -19,24 +19,25 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
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.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -53,32 +54,50 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import com.android.systemui.Flags as AConfigFlags
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardRootViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val communalRepository = kosmos.communalRepository
- private val screenOffAnimationController = kosmos.screenOffAnimationController
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
- private val dozeParameters = kosmos.dozeParameters
- private val shadeRepository = kosmos.fakeShadeRepository
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val communalRepository by lazy { kosmos.communalRepository }
+ private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+ private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
+ private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor }
+ private val dozeParameters by lazy { kosmos.dozeParameters }
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val underTest by lazy { kosmos.keyguardRootViewModel }
private val viewState = ViewStateAccessor()
+ // add to init block
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
+ )
+ }
}
@Test
@@ -336,10 +355,10 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope,
)
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(1f)
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -356,11 +375,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
)
// Open the shade.
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
assertThat(alpha).isEqualTo(0f)
// Close the shade, alpha returns to 1.
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(1f)
}
@@ -377,11 +396,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
)
// Open the shade.
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
assertThat(alpha).isEqualTo(0f)
// Close the shade, alpha is still 0 since we're not on the lockscreen.
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(0f)
}
@@ -400,7 +419,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -419,7 +438,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -438,7 +457,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 49073595af0c..ec2cb049836f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -16,13 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.shared.model.ClockSize
@@ -40,15 +42,29 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenContentViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
lateinit var underTest: LockscreenContentViewModel
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
with(kosmos) {
@@ -77,6 +93,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun clockSize_withLargeClock_true() =
with(kosmos) {
testScope.runTest {
@@ -87,6 +104,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun clockSize_withSmallClock_false() =
with(kosmos) {
testScope.runTest {
@@ -109,6 +127,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
@@ -119,6 +138,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 776f1a55fdcb..bc9d257b3836 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
@@ -57,6 +58,7 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
+@EnableSceneContainer
class LockscreenSceneViewModelTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
index 769a54aee862..13d6411382cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -58,13 +58,16 @@ import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupReposi
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class InternetTileDataInteractorTest : SysuiTestCase() {
@@ -141,6 +144,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
underTest =
InternetTileDataInteractor(
context,
+ testScope.coroutineContext,
testScope.backgroundScope,
airplaneModeRepository,
connectivityRepository,
@@ -433,8 +437,44 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
.isEqualTo(expectedCd)
}
+ /**
+ * We expect a RuntimeException because [underTest] instantiates a SignalDrawable on the
+ * provided context, and so the SignalDrawable constructor attempts to instantiate a Handler()
+ * on the mentioned context. Since that context does not have a looper assigned to it, the
+ * handler instantiation will throw a RuntimeException.
+ *
+ * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception
+ * So either we should make Robolectric behvase similar to the device test, or change this
+ * test to look for a different signal than the exception, when run by Robolectric. For now
+ * we just assume the test is not Robolectric.
+ */
+ @Test(expected = java.lang.RuntimeException::class)
+ fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
+ testScope.runTest {
+ assumeFalse(isRobolectricTest());
+
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ runCurrent()
+ }
+
+ /**
+ * See [mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException] for description of the
+ * problem this test solves. The solution here is to assign a looper to the context via
+ * RunWithLooper. In the production code, the solution is to use a Main CoroutineContext for
+ * creating the SignalDrawable.
+ */
+ @TestableLooper.RunWithLooper
@Test
- fun mobileDefault_usesNetworkNameAndIcon() =
+ fun mobileDefault_run_withLooper_usesNetworkNameAndIcon() =
testScope.runTest {
val latest by
collectLastValue(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 818c19ca9de0..4e0685556145 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -16,22 +16,26 @@
package com.android.systemui.volume.panel.ui.viewmodel
+import android.content.Intent
+import android.content.applicationContext
import android.content.res.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.volume.panel.componentByKey
import com.android.systemui.volume.panel.componentsLayoutManager
import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
import com.android.systemui.volume.panel.unavailableCriteria
+import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -51,67 +55,49 @@ class VolumePanelViewModelTest : SysuiTestCase() {
private lateinit var underTest: VolumePanelViewModel
- private fun initUnderTest() {
- underTest =
- VolumePanelViewModel(
- testableResources.resources,
- kosmos.testScope.backgroundScope,
- KosmosVolumePanelComponentFactory(kosmos),
- kosmos.fakeConfigurationController,
- )
- }
-
@Test
- fun dismissingPanel_changesVisibility() {
- with(kosmos) {
- testScope.runTest {
- initUnderTest()
- assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+ fun dismissingPanel_changesVisibility() = test {
+ testScope.runTest {
+ assertThat(underTest.volumePanelState.value.isVisible).isTrue()
- underTest.dismissPanel()
- runCurrent()
+ underTest.dismissPanel()
+ runCurrent()
- assertThat(underTest.volumePanelState.value.isVisible).isFalse()
- }
+ assertThat(underTest.volumePanelState.value.isVisible).isFalse()
}
}
@Test
- fun orientationChanges_panelOrientationChanges() {
- with(kosmos) {
- testScope.runTest {
- initUnderTest()
- val volumePanelState by collectLastValue(underTest.volumePanelState)
- testableResources.overrideConfiguration(
- Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
- )
- assertThat(volumePanelState!!.orientation)
- .isEqualTo(Configuration.ORIENTATION_PORTRAIT)
+ fun orientationChanges_panelOrientationChanges() = test {
+ testScope.runTest {
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ testableResources.overrideConfiguration(
+ Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+ )
+ assertThat(volumePanelState!!.orientation).isEqualTo(Configuration.ORIENTATION_PORTRAIT)
- fakeConfigurationController.onConfigurationChanged(
- Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
- )
- runCurrent()
+ fakeConfigurationController.onConfigurationChanged(
+ Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
+ )
+ runCurrent()
- assertThat(volumePanelState!!.orientation)
- .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
- }
+ assertThat(volumePanelState!!.orientation)
+ .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
}
}
@Test
- fun components_areReturned() {
- with(kosmos) {
+ fun components_areReturned() =
+ test({
+ componentByKey =
+ mapOf(
+ COMPONENT_1 to mockVolumePanelUiComponentProvider,
+ COMPONENT_2 to mockVolumePanelUiComponentProvider,
+ BOTTOM_BAR to mockVolumePanelUiComponentProvider,
+ )
+ criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+ }) {
testScope.runTest {
- componentByKey =
- mapOf(
- COMPONENT_1 to mockVolumePanelUiComponentProvider,
- COMPONENT_2 to mockVolumePanelUiComponentProvider,
- BOTTOM_BAR to mockVolumePanelUiComponentProvider,
- )
- criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
- initUnderTest()
-
val componentsLayout by collectLastValue(underTest.componentsLayout)
runCurrent()
@@ -124,11 +110,45 @@ class VolumePanelViewModelTest : SysuiTestCase() {
assertThat(componentsLayout!!.bottomBarComponent.isVisible).isTrue()
}
}
+
+ @Test
+ fun dismissPanel_dismissesPanel() = test {
+ testScope.runTest {
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ underTest.dismissPanel()
+ runCurrent()
+
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun dismissBroadcast_dismissesPanel() = test {
+ testScope.runTest {
+ runCurrent() // run the flows to let allow the receiver to be registered
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(DISMISS_ACTION),
+ )
+ runCurrent()
+
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
}
+ private fun test(setup: Kosmos.() -> Unit = {}, test: Kosmos.() -> Unit) =
+ with(kosmos) {
+ setup()
+ underTest = volumePanelViewModel
+ test()
+ }
+
private companion object {
const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+
+ const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index 3b6b5a0db393..2a8f1b596711 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -66,6 +66,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -127,4 +128,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPasswordView> \ No newline at end of file
+</com.android.keyguard.KeyguardPasswordView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 5aac65396532..76f6f599c54c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -31,6 +31,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index 6780e5721af3..5879c110d8a1 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -67,6 +67,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -107,4 +108,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPatternView> \ No newline at end of file
+</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index d991581c2665..3f7b02835357 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -35,6 +35,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index 6c79d5a6095a..b464fb3bafed 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -74,6 +74,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -241,4 +242,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPINView> \ No newline at end of file
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index f3cd9e49b49c..21580731aed2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -32,6 +32,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
index 045c19eb09dc..65d6e2863130 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
@@ -14,36 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
- android:fillAlpha="0.3"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
index 5e012ab7edbd..0399b81b859e 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
@@ -16,34 +16,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
-
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
index d8a9a703260d..b0acbdccfb4e 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
@@ -16,32 +16,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
index a80d3b4d0fa8..2cab0434d649 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -16,28 +16,11 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:alpha="0.3"
- >
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index d13efd25e353..f644584f747a 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -174,7 +174,7 @@ android:layout_height="match_parent">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index a6e660f940df..46b8e4665a22 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -152,7 +152,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index c724d24a31da..d51fe5849133 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -150,7 +150,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 19273ec3af82..6bfd0887c404 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -461,10 +461,11 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_screenshotEditor" translatable="false"></string>
- <!-- ComponentName for the file browsing app that the system would expect to be used in work
- profile. The icon for this app will be shown to the user when informing them that a
- screenshot has been saved to work profile. If blank, a default icon will be shown. -->
- <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
+ <!-- ComponentName for the file browsing app that the system would expect to be used for
+ screenshots. The icon for this app will be shown to the user when informing them that a
+ screenshot has been saved to a different profile (e.g. work profile). If blank, a default
+ icon will be shown. -->
+ <string name="config_screenshotFilesApp" translatable="false"></string>
<!-- The component name of the screenshot editing activity that provides the App Clips flow.
The App Clips flow includes taking a screenshot, showing user screenshot cropping activity
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b8e78a44a8ed..40d863d1178e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -257,6 +257,8 @@
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
<string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
+ <!-- Notification displayed when a screenshot is saved in the private profile. [CHAR LIMIT=NONE] -->
+ <string name="screenshot_private_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the private profile</string>
<!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
<string name="screenshot_default_files_app_name">Files</string>
<!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
@@ -1182,6 +1184,8 @@
<string name="accessibility_action_label_edit_widgets">Customize widgets</string>
<!-- Accessibility content description for communal hub. [CHAR LIMIT=NONE] -->
<string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string>
+ <!-- Label for accessibility action to select a widget in edit mode. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_select_widget">select widget</string>
<!-- Related to user switcher --><skip/>
@@ -1719,9 +1723,7 @@
<string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
<!-- Text displayed indicating that the user is connected to a satellite signal. -->
- <string name="satellite_connected_carrier_text">Connected to satellite</string>
- <!-- Text displayed indicating that the user is not connected to a satellite signal. -->
- <string name="satellite_not_connected_carrier_text">Not connected to satellite</string>
+ <string name="satellite_connected_carrier_text">Satellite SOS</string>
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
@@ -2261,6 +2263,9 @@
<!-- Accessibility description when QS tile is to be added, indicating the destination position [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_add_to_position">Add to position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+ <!-- Accessibility description when QS tile would be added or moved, but the current position is not valid for adding or moving to [CHAR LIMIT=NONE] -->
+ <string name="accessibilit_qs_edit_tile_add_move_invalid_position">Position invalid.</string>
+
<!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index c6716e44899f..68a69d34b75a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -192,6 +192,7 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con
private final ShadeController mShadeController;
private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor;
private final StatusBarWindowCallback mNotificationShadeCallback;
+ private final ScreenshotHelper mScreenshotHelper;
private boolean mDismissNotificationShadeActionRegistered;
@Inject
@@ -221,6 +222,7 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
panelExpanded, isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
+ mScreenshotHelper = new ScreenshotHelper(mContext);
}
@Override
@@ -516,8 +518,7 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con
}
private void handleTakeScreenshot() {
- ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(
+ mScreenshotHelper.takeScreenshot(
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index a21114798c8f..da5695163cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -112,10 +112,6 @@ object BiometricViewBinder {
}
// set selected to enable marquee unless a screen reader is enabled
- logoView.isSelected =
- !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
- logoDescriptionView.isSelected =
- !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
titleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
@@ -419,19 +415,6 @@ object BiometricViewBinder {
indicatorMessageView.isSelected =
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
-
- /**
- * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
- * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
- * b/281765653#comment18) Using {@link View#announceForAccessibility}
- * instead as workaround since sending events exceeding this frequency is
- * required.
- */
- indicatorMessageView?.text?.let {
- if (it.isNotBlank()) {
- view.announceForAccessibility(it)
- }
- }
}
}
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 511bdc4c6cea..db251fde187d 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
@@ -18,6 +18,7 @@ package com.android.systemui.communal.ui.viewmodel
import android.content.ComponentName
import android.os.UserHandle
+import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
@@ -37,8 +38,8 @@ abstract class BaseCommunalViewModel(
) {
val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
- /** Whether communal hub can be focused to enable accessibility actions. */
- val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal
+ /** Whether communal hub should be focused by accessibility tools. */
+ open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
@@ -49,6 +50,9 @@ abstract class BaseCommunalViewModel(
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ /** Accessibility delegate to be set on CommunalAppWidgetHostView. */
+ open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
+
fun signalUserInteraction() {
communalInteractor.signalUserInteraction()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 9dacf8cc4c5a..1120466c7acc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,12 +16,18 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.res.Resources
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -29,7 +35,9 @@ import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
@@ -54,6 +62,8 @@ class CommunalViewModel
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ @Main private val resources: Resources,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -93,6 +103,37 @@ constructor(
private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null)
override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow()
+ // The widget is focusable for accessibility when the hub is fully visible and shade is not
+ // opened.
+ override val isFocusable: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ communalInteractor.isIdleOnCommunal,
+ shadeInteractor.isAnyFullyExpanded,
+ ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
+ transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded
+ }
+ .distinctUntilChanged()
+
+ override val widgetAccessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Hint user to long press in order to enter edit mode
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ resources
+ .getString(R.string.accessibility_action_label_edit_widgets)
+ .lowercase()
+ )
+ )
+ }
+ }
+
private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
@@ -191,6 +232,14 @@ constructor(
return !shadeInteractor.isAnyFullyExpanded.value
}
+ /**
+ * Whether touches should be disabled in communal.
+ *
+ * This is needed because the notification shade does not block touches in blank areas and these
+ * fall through to the glanceable hub, which we don't want.
+ */
+ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 2fa42ec3904d..7ced9322ed38 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -24,7 +24,6 @@ import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.screenshot.SmartActionsReceiver;
-import com.android.systemui.volume.VolumePanelDialogReceiver;
import dagger.Binds;
import dagger.Module;
@@ -59,15 +58,6 @@ public abstract class DefaultBroadcastReceiverBinder {
*/
@Binds
@IntoMap
- @ClassKey(VolumePanelDialogReceiver.class)
- public abstract BroadcastReceiver bindVolumePanelDialogReceiver(
- VolumePanelDialogReceiver broadcastReceiver);
-
- /**
- *
- */
- @Binds
- @IntoMap
@ClassKey(PeopleSpaceWidgetPinnedReceiver.class)
public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
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 1fba737c0566..c2843d839d15 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
@@ -168,11 +168,13 @@ constructor(
keyguardInteractor.isKeyguardOccluded
.filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
.collect {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- ownerReason = "isOccluded = true",
- )
+ if (!maybeHandleInsecurePowerGesture()) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ ownerReason = "isOccluded = true",
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 4abd6c6a6453..f3856710b1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -77,7 +77,9 @@ constructor(
// transition, to ensure we don't transition while moving between, for example,
// *_BOUNCER -> LOCKSCREEN.
return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
- KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+ KeyguardState.deviceIsAsleepInState(
+ transitionInteractor.currentTransitionInfoInternal.value.to
+ )
}
/**
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 2850165b0d1a..b2a24ca85b07 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
@@ -225,10 +225,12 @@ sealed class TransitionInteractor(
if (!KeyguardWmStateRefactor.isEnabled) {
scope.launch {
keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
+ if (!maybeHandleInsecurePowerGesture()) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
index f5cd7676e4b1..63bfba604b76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -19,10 +19,10 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.hideAffordancesRequest
import javax.inject.Inject
@@ -38,17 +38,16 @@ import kotlinx.coroutines.flow.onStart
class UdfpsKeyguardInteractor
@Inject
constructor(
- configRepo: ConfigurationRepository,
burnInInteractor: BurnInInteractor,
keyguardInteractor: KeyguardInteractor,
- shadeRepository: ShadeRepository,
+ shadeInteractor: ShadeInteractor,
+ shadeLockscreenInteractor: ShadeLockscreenInteractor,
dialogManager: SystemUIDialogManager,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
val dozeAmount = keyguardInteractor.dozeAmount
- val scaleForResolution = configRepo.scaleForResolution
/** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
val burnInOffsets: Flow<Offsets> =
@@ -68,13 +67,14 @@ constructor(
val dialogHideAffordancesRequest: Flow<Boolean> = dialogManager.hideAffordancesRequest
val qsProgress: Flow<Float> =
- shadeRepository.qsExpansion // swipe from top of LS
+ shadeInteractor.qsExpansion // swipe from top of LS
.map { (it * 2).coerceIn(0f, 1f) }
.onStart { emit(0f) }
val shadeExpansion: Flow<Float> =
combine(
- shadeRepository.udfpsTransitionToFullShadeProgress, // swipe from middle of LS
+ shadeLockscreenInteractor
+ .udfpsTransitionToFullShadeProgress, // swipe from middle of LS
keyguardInteractor.statusBarState, // quick swipe from middle of LS
) { shadeProgress, statusBarState ->
if (statusBarState == StatusBarState.SHADE_LOCKED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index 7e39a884a69e..adc090de1d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -57,5 +57,15 @@ constructor(
)
}
+ fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ return transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStart = { startAlpha = viewState.alpha() },
+ onStep = { startAlpha },
+ onFinish = { 1f },
+ )
+ }
+
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
index d863dcce4fc7..710142b6705d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
@@ -96,7 +96,7 @@ public class NotificationHelper {
if (messages2 == null) {
return -1;
}
- return (int) (n2.when - n1.when);
+ return (int) (n2.getWhen() - n1.getWhen());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 58858df32f1b..829c419fc07f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -332,6 +332,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
return mTiles.size();
}
+ public int getItemCountForAccessibility() {
+ if (mAccessibilityAction == ACTION_MOVE) {
+ return mEditIndex;
+ } else {
+ return getItemCount();
+ }
+ }
+
@Override
public boolean onFailedToRecycleView(Holder holder) {
holder.stopDrag();
@@ -406,6 +414,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
} else if (selectable && mAccessibilityAction == ACTION_MOVE) {
info.state.contentDescription = mContext.getString(
R.string.accessibility_qs_edit_tile_move_to_position, position);
+ } else if (!selectable && (mAccessibilityAction == ACTION_MOVE
+ || mAccessibilityAction == ACTION_ADD)) {
+ info.state.contentDescription = mContext.getString(
+ R.string.accessibilit_qs_edit_tile_add_move_invalid_position);
} else {
info.state.contentDescription = info.state.label;
}
@@ -424,14 +436,15 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
holder.mTileView.setOnClickListener(null);
holder.mTileView.setFocusable(true);
holder.mTileView.setFocusableInTouchMode(true);
+ holder.mTileView.setAccessibilityTraversalBefore(View.NO_ID);
if (mAccessibilityAction != ACTION_NONE) {
holder.mTileView.setClickable(selectable);
holder.mTileView.setFocusable(selectable);
holder.mTileView.setFocusableInTouchMode(selectable);
- holder.mTileView.setImportantForAccessibility(selectable
- ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
- : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+// holder.mTileView.setImportantForAccessibility(selectable
+// ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+// : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
if (selectable) {
holder.mTileView.setOnClickListener(new OnClickListener() {
@Override
@@ -911,4 +924,5 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
int estimatedTileViewHeight = mTempTextView.getMeasuredHeight() * 2 + padding * 2;
mMinTileViewHeight = Math.max(minHeight, estimatedTileViewHeight);
}
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index d5b05ef68288..60469c070bf7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -116,8 +116,6 @@ import javax.inject.Inject;
public class InternetDialogController implements AccessPointController.AccessPointCallback {
private static final String TAG = "InternetDialogController";
- private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
- "android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
/**
@@ -361,7 +359,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi
@VisibleForTesting
protected Intent getSettingsIntent() {
- return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return new Intent(Settings.ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
index fdc596b3030c..eec5d3d915f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -26,6 +26,7 @@ import com.android.systemui.common.shared.model.ContentDescription.Companion.loa
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -38,7 +39,9 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -48,6 +51,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
/** Observes internet state changes providing the [InternetTileModel]. */
@@ -55,6 +59,7 @@ class InternetTileDataInteractor
@Inject
constructor(
private val context: Context,
+ @Main private val mainCoroutineContext: CoroutineContext,
@Application private val scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
private val connectivityRepository: ConnectivityRepository,
@@ -111,42 +116,48 @@ constructor(
notConnectedFlow
} else {
combine(
- it.networkName,
- it.signalLevelIcon,
- mobileDataContentName,
- ) { networkNameModel, signalIcon, dataContentDescription ->
- when (signalIcon) {
- is SignalIconModel.Cellular -> {
- val secondary =
- mobileDataContentConcat(
- networkNameModel.name,
- dataContentDescription
- )
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ Triple(networkNameModel, signalIcon, dataContentDescription)
+ }
+ .mapLatestConflated { (networkNameModel, signalIcon, dataContentDescription) ->
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
- val stateLevel = signalIcon.level
- val drawable = SignalDrawable(context)
- drawable.setLevel(stateLevel)
- val loadedIcon = Icon.Loaded(drawable, null)
+ val drawable =
+ withContext(mainCoroutineContext) { SignalDrawable(context) }
+ drawable.setLevel(signalIcon.level)
+ val loadedIcon = Icon.Loaded(drawable, null)
- InternetTileModel.Active(
- secondaryTitle = secondary,
- icon = loadedIcon,
- stateDescription = ContentDescription.Loaded(secondary.toString()),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
- }
- is SignalIconModel.Satellite -> {
- val secondary =
- signalIcon.icon.contentDescription.loadContentDescription(context)
- InternetTileModel.Active(
- secondaryTitle = secondary,
- iconId = signalIcon.icon.res,
- stateDescription = ContentDescription.Loaded(secondary),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = loadedIcon,
+ stateDescription =
+ ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(
+ context
+ )
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
}
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 864f29a5c5e0..d4e711e38b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -229,6 +229,8 @@ public class ImageExporter {
return CallbackToFutureAdapter.getFuture(
(completer) -> {
executor.execute(() -> {
+ // save images as quickly as possible on the background thread
+ Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
try {
completer.set(task.execute());
} catch (ImageExportException | InterruptedException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 7130fa1a1bd3..596046268984 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -3,15 +3,19 @@ package com.android.systemui.screenshot
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.os.UserHandle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import com.android.systemui.screenshot.message.ProfileMessageController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* MessageContainerController controls the display of content in the screenshot message container.
@@ -20,7 +24,9 @@ class MessageContainerController
@Inject
constructor(
private val workProfileMessageController: WorkProfileMessageController,
+ private val profileMessageController: ProfileMessageController,
private val screenshotDetectionController: ScreenshotDetectionController,
+ @Application private val mainScope: CoroutineScope,
) {
private lateinit var container: ViewGroup
private lateinit var guideline: Guideline
@@ -42,43 +48,52 @@ constructor(
detectionNoticeView.visibility = View.GONE
}
- // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on.
- fun onScreenshotTaken(userHandle: UserHandle) {
- val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle)
- if (workProfileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
-
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
- animateInMessageContainer()
- }
- }
-
fun onScreenshotTaken(screenshot: ScreenshotData) {
- val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> =
- screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+ if (screenshotPrivateProfileBehaviorFix()) {
+ mainScope.launch {
+ val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+
+ // If profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (profileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ profileMessageController.bindView(workProfileFirstRunView, profileData) {
+ animateOutMessageContainer()
+ }
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
+ }
+ } else {
+ val workProfileData =
+ workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- // If work profile first run needs to show, bias towards that, otherwise show screenshot
- // detection notification if needed.
- if (workProfileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
- animateInMessageContainer()
- } else if (notifiedApps.isNotEmpty()) {
- detectionNoticeView.visibility = View.VISIBLE
- workProfileFirstRunView.visibility = View.GONE
- screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
- animateInMessageContainer()
+ // If work profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (workProfileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ workProfileMessageController.populateView(
+ workProfileFirstRunView,
+ workProfileData,
+ this::animateOutMessageContainer
+ )
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index c801ca5aa8a1..b93aedd9dd52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -53,8 +53,9 @@ constructor(
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
- val fileManager = fileManagerComponentName()
- ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
+ val fileManager =
+ fileManagerComponentName()
+ ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
try {
val info = packageManager.getActivityInfo(fileManager, ComponentInfoFlags.of(0L))
val icon = packageManager.getActivityIcon(fileManager)
@@ -103,9 +104,7 @@ constructor(
context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
private fun fileManagerComponentName() =
- ComponentName.unflattenFromString(
- context.getString(R.string.config_sceenshotWorkProfileFilesApp)
- )
+ ComponentName.unflattenFromString(context.getString(R.string.config_screenshotFilesApp))
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 9b8d0472b583..8235325fffad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -38,6 +38,7 @@ import com.android.systemui.screenshot.TakeScreenshotExecutorImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
+import com.android.systemui.screenshot.message.MessageModule;
import com.android.systemui.screenshot.policy.ScreenshotPolicyModule;
import com.android.systemui.screenshot.proxy.SystemUiProxyModule;
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel;
@@ -51,7 +52,7 @@ import dagger.multibindings.IntoMap;
/**
* Defines injectable resources for Screenshots
*/
-@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class})
+@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class, MessageModule.class})
public abstract class ScreenshotModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt
new file mode 100644
index 000000000000..9d0f87f30efc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.screenshot.message
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MessageModule {
+ @Binds
+ @SysUISingleton
+ fun bindProfileFirstRunResources(
+ impl: ProfileFirstRunFileResourcesImpl
+ ): ProfileFirstRunFileResources
+
+ @Binds
+ @SysUISingleton
+ fun bindPackageLabelIconProvider(impl: PackageLabelIconProviderImpl): PackageLabelIconProvider
+
+ @Binds
+ @SysUISingleton
+ fun bindProfileFirstRunSettings(impl: ProfileFirstRunSettingsImpl): ProfileFirstRunSettings
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt
new file mode 100644
index 000000000000..fd073aecb50e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.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.screenshot.message
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import javax.inject.Inject
+
+data class LabeledIcon(
+ val label: CharSequence,
+ val badgedIcon: Drawable?,
+)
+
+/** An object that can fetch a label and icon for a given component. */
+interface PackageLabelIconProvider {
+ /**
+ * @return the label and icon for the given component.
+ * @throws PackageManager.NameNotFoundException if the component was not found.
+ */
+ suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon
+}
+
+class PackageLabelIconProviderImpl @Inject constructor(private val packageManager: PackageManager) :
+ PackageLabelIconProvider {
+
+ override suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon {
+ val info =
+ packageManager.getActivityInfo(componentName, PackageManager.ComponentInfoFlags.of(0L))
+ val icon = packageManager.getActivityIcon(componentName)
+ val badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
+ val label = info.loadLabel(packageManager)
+ return LabeledIcon(label, badgedIcon)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt
new file mode 100644
index 000000000000..e58c76de5752
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.screenshot.message
+
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Provides various configuration resource values for the profile first run flow. */
+interface ProfileFirstRunFileResources {
+ /** @return the ComponentName for the Files app, if available. */
+ fun fileManagerComponentName(): ComponentName?
+
+ /** @return a default LabeledIcon describing the files app */
+ fun defaultFileApp(): LabeledIcon
+}
+
+class ProfileFirstRunFileResourcesImpl @Inject constructor(private val context: Context) :
+ ProfileFirstRunFileResources {
+ override fun fileManagerComponentName() =
+ ComponentName.unflattenFromString(context.getString(R.string.config_screenshotFilesApp))
+
+ override fun defaultFileApp() =
+ LabeledIcon(
+ context.getString(R.string.screenshot_default_files_app_name),
+ badgedIcon = null
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt
new file mode 100644
index 000000000000..5ec14a3b5834
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.screenshot.message
+
+import android.content.Context
+import javax.inject.Inject
+
+/**
+ * An interfaces for the settings related to the profile first run experience, storing a bit
+ * indicating whether the user has already dismissed the message for the given profile.
+ */
+interface ProfileFirstRunSettings {
+ /** @return true if the user has already dismissed the first run message for this profile. */
+ fun messageAlreadyDismissed(profileType: ProfileMessageController.FirstRunProfile): Boolean
+ /**
+ * Update storage to reflect the fact that the user has dismissed a first run message for the
+ * given profile.
+ */
+ fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile)
+}
+
+class ProfileFirstRunSettingsImpl @Inject constructor(private val context: Context) :
+ ProfileFirstRunSettings {
+
+ override fun messageAlreadyDismissed(
+ profileType: ProfileMessageController.FirstRunProfile
+ ): Boolean {
+ val preferenceKey = preferenceKey(profileType)
+ return sharedPreference().getBoolean(preferenceKey, false)
+ }
+
+ override fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile) {
+ val preferenceKey = preferenceKey(profileType)
+ val editor = sharedPreference().edit()
+ editor.putBoolean(preferenceKey, true)
+ editor.apply()
+ }
+
+ private fun preferenceKey(profileType: ProfileMessageController.FirstRunProfile): String {
+ return when (profileType) {
+ ProfileMessageController.FirstRunProfile.WORK -> WORK_PREFERENCE_KEY
+ ProfileMessageController.FirstRunProfile.PRIVATE -> PRIVATE_PREFERENCE_KEY
+ }
+ }
+
+ private fun sharedPreference() =
+ context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ companion object {
+ const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
+ const val WORK_PREFERENCE_KEY = "work_profile_first_run"
+ const val PRIVATE_PREFERENCE_KEY = "private_profile_first_run"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt
new file mode 100644
index 000000000000..9212a6a875da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.screenshot.message
+
+import android.os.UserHandle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import javax.inject.Inject
+
+/**
+ * Handles work profile and private profile first run, determining whether a first run UI should be
+ * shown and populating that UI if needed.
+ */
+class ProfileMessageController
+@Inject
+constructor(
+ private val packageLabelIconProvider: PackageLabelIconProvider,
+ private val fileResources: ProfileFirstRunFileResources,
+ private val firstRunSettings: ProfileFirstRunSettings,
+ private val profileTypes: ProfileTypeRepository,
+) {
+
+ /**
+ * @return a populated ProfileFirstRunData object if a profile first run message should be
+ * shown, otherwise null.
+ */
+ suspend fun onScreenshotTaken(userHandle: UserHandle?): ProfileFirstRunData? {
+ if (userHandle == null) return null
+ val profileType =
+ when (profileTypes.getProfileType(userHandle.identifier)) {
+ ProfileType.WORK -> FirstRunProfile.WORK
+ ProfileType.PRIVATE -> FirstRunProfile.PRIVATE
+ else -> return null
+ }
+
+ if (firstRunSettings.messageAlreadyDismissed(profileType)) {
+ return null
+ }
+
+ val fileApp =
+ runCatching {
+ fileResources.fileManagerComponentName()?.let { fileManager ->
+ packageLabelIconProvider.getPackageLabelIcon(fileManager, userHandle)
+ }
+ }
+ .getOrNull() ?: fileResources.defaultFileApp()
+
+ return ProfileFirstRunData(fileApp, profileType)
+ }
+
+ /**
+ * Use the provided ProfileFirstRunData to populate the profile first run UI in the given view.
+ */
+ fun bindView(view: ViewGroup, data: ProfileFirstRunData, animateOut: () -> Unit) {
+ if (data.labeledIcon.badgedIcon != null) {
+ // Replace the default icon if one is provided.
+ val imageView = view.requireViewById<ImageView>(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(data.labeledIcon.badgedIcon)
+ }
+ val messageContent = view.requireViewById<TextView>(R.id.screenshot_message_content)
+ messageContent.text =
+ view.context.getString(messageTemplate(data.profileType), data.labeledIcon.label)
+ view.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+ animateOut()
+ firstRunSettings.onMessageDismissed(data.profileType)
+ }
+ }
+
+ private fun messageTemplate(profile: FirstRunProfile): Int {
+ return when (profile) {
+ FirstRunProfile.WORK -> R.string.screenshot_work_profile_notification
+ FirstRunProfile.PRIVATE -> R.string.screenshot_private_profile_notification
+ }
+ }
+
+ data class ProfileFirstRunData(
+ val labeledIcon: LabeledIcon,
+ val profileType: FirstRunProfile,
+ )
+
+ enum class FirstRunProfile {
+ WORK,
+ PRIVATE
+ }
+
+ companion object {
+ const val TAG = "PrivateProfileMessageCtrl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 67211b11a32f..403bece4a7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -244,6 +244,7 @@ import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.StateFlow;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -4055,6 +4056,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
@Override
+ public StateFlow<Float> getUdfpsTransitionToFullShadeProgress() {
+ return mShadeRepository.getUdfpsTransitionToFullShadeProgress();
+ }
+
+ @Override
public Flow<Float> getLegacyPanelExpansion() {
return mShadeRepository.getLegacyShadeExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 0c41efd513ad..9322d31fa2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
/** Empty implementation of ShadeViewController for variants with no shade. */
@@ -92,6 +93,7 @@ open class ShadeViewControllerEmptyImpl @Inject constructor() :
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
@Deprecated("Use SceneInteractor.currentScene instead.")
override val legacyPanelExpansion = flowOf(0f)
+ override val udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
}
class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5c79e1ee84f3..b934d63c17c5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -28,7 +28,7 @@ interface ShadeRepository {
* Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick
* Settings can be expanded without the full shade expansion.
*/
- val qsExpansion: StateFlow<Float>
+ @Deprecated("Use ShadeInteractor.qsExpansion instead") val qsExpansion: StateFlow<Float>
/** Amount shade has expanded with regard to the UDFPS location */
val udfpsTransitionToFullShadeProgress: StateFlow<Float>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
index 2611092553ed..987c01699f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
@@ -15,9 +15,14 @@
*/
package com.android.systemui.shade.domain.interactor
+import kotlinx.coroutines.flow.StateFlow
+
/** Allows the lockscreen to control the shade. */
interface ShadeLockscreenInteractor {
+ /** Amount shade has expanded with regard to the UDFPS location */
+ val udfpsTransitionToFullShadeProgress: StateFlow<Float>
+
/**
* Expand shade so that notifications are visible. Non-split shade: just expanding shade or
* collapsing QS when they're expanded. Split shade: only expanding shade, notifications are
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 318da557ed2a..6a8b9eec140c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -20,6 +20,7 @@ import com.android.keyguard.LockIconViewController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -32,7 +33,12 @@ constructor(
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val lockIconViewController: LockIconViewController,
+ shadeRepository: ShadeRepository,
) : ShadeLockscreenInteractor {
+
+ override val udfpsTransitionToFullShadeProgress =
+ shadeRepository.udfpsTransitionToFullShadeProgress
+
override fun expandToNotifications() {
changeToShadeScene()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d465973a5d12..4d7dacd336ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -29,6 +29,7 @@ import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.shared.model.MediaData;
@@ -47,6 +48,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* Handles tasks and state related to media notifications. For example, there is a 'current' media
@@ -75,6 +77,8 @@ public class NotificationMediaManager implements Dumpable {
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
+ private final Executor mBackgroundExecutor;
+
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
private String mMediaNotificationKey;
@@ -115,13 +119,15 @@ public class NotificationMediaManager implements Dumpable {
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor backgroundExecutor) {
mContext = context;
mMediaListeners = new ArrayList<>();
mVisibilityProvider = visibilityProvider;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
+ mBackgroundExecutor = backgroundExecutor;
setupNotifPipeline();
@@ -381,9 +387,11 @@ public class NotificationMediaManager implements Dumpable {
Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ mMediaController.getPackageName());
}
- mMediaController.unregisterCallback(mMediaListener);
+ mBackgroundExecutor.execute(() -> {
+ mMediaController.unregisterCallback(mMediaListener);
+ mMediaController = null;
+ });
}
- mMediaController = null;
}
public interface MediaListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 8a53e0ce94c4..c17da4b3b4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,6 +31,7 @@ import com.android.systemui.animation.AnimationFeatureFlags;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -68,6 +69,8 @@ import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import java.util.concurrent.Executor;
+
import javax.inject.Provider;
/**
@@ -94,14 +97,16 @@ public interface CentralSurfacesDependenciesModule {
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor backgroundExecutor) {
return new NotificationMediaManager(
context,
visibilityProvider,
notifPipeline,
notifCollection,
mediaDataManager,
- dumpManager);
+ dumpManager,
+ backgroundExecutor);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 240ae0c13ac7..9c1d0735a65b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1243,8 +1243,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
if (cmp != 0) return cmp;
cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ o1.getRepresentativeEntry().getSbn().getNotification().getWhen(),
+ o2.getRepresentativeEntry().getSbn().getNotification().getWhen());
+
return cmp;
};
@@ -1256,8 +1257,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
if (cmp != 0) return cmp;
cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ o1.getRepresentativeEntry().getSbn().getNotification().getWhen(),
+ o2.getRepresentativeEntry().getSbn().getNotification().getWhen());
return cmp;
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
index 5ce1db2b6acd..f253100b3661 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -89,7 +89,7 @@ constructor(
var futureTime = Long.MAX_VALUE
groupEntry.children
.asSequence()
- .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
+ .mapNotNull { child -> child.sbn.notification.getWhen().takeIf { it > 0 } }
.forEach { time ->
val isInThePast = currentTimeMillis - time > 0
if (isInThePast) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index fb67f7cc0b0f..f98f77ec4ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -296,7 +296,9 @@ class HeadsUpCoordinator @Inject constructor(
locationLookupByKey: (String) -> GroupLocation,
): NotificationEntry? = postedEntries.asSequence()
.filter { posted -> !posted.entry.sbn.notification.isGroupSummary }
- .sortedBy { posted -> -posted.entry.sbn.notification.`when` }
+ .sortedBy { posted ->
+ -posted.entry.sbn.notification.getWhen()
+ }
.firstOrNull()
?.let { posted ->
posted.entry.takeIf { entry ->
@@ -317,7 +319,7 @@ class HeadsUpCoordinator @Inject constructor(
.filter { locationLookupByKey(it.key) != GroupLocation.Detached }
.sortedWith(compareBy(
{ !mPostedEntries.contains(it.key) },
- { -it.sbn.notification.`when` },
+ { -it.sbn.notification.getWhen() },
))
.firstOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9619acaed441..bfc5932c1db0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -153,12 +153,12 @@ class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
uiEventId = HUN_SUPPRESSED_OLD_WHEN
) {
private fun whenAge(entry: NotificationEntry) =
- systemClock.currentTimeMillis() - entry.sbn.notification.`when`
+ systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
override fun shouldSuppress(entry: NotificationEntry): Boolean =
when {
// Ignore a "when" of 0, as it is unlikely to be a meaningful timestamp.
- entry.sbn.notification.`when` <= 0L -> false
+ entry.sbn.notification.getWhen() <= 0L -> false
// Assume all HUNs with FSIs, foreground services, or user-initiated jobs are
// time-sensitive, regardless of their "when".
@@ -278,7 +278,7 @@ class AvalancheSuppressor(
private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
- entry.sbn.notification.`when` > avalancheProvider.startTime
+ entry.sbn.notification.getWhen() > avalancheProvider.startTime
) {
return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index d5911141fc0f..9c6a42384a53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -623,7 +623,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
return false;
}
- final long when = notification.when;
+ final long when = notification.getWhen();
final long now = mSystemClock.currentTimeMillis();
final long age = now - when;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index bdeaabf53b07..5e3df7b5e60f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2769,7 +2769,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
if (!mIsSummaryWithChildren && wasSummary) {
// Reset the 'when' once the row stops being a summary
- mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().when);
+ mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 37bbbd0466d4..ac4bd09b1687 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -41,6 +41,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -106,6 +107,7 @@ constructor(
private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
+ private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
@@ -464,6 +466,7 @@ constructor(
val alphaTransitions =
merge(
alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
+ aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b2b2ceaa9017..7630d43c3c7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -855,6 +855,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mBubblesOptional.ifPresent(this::initBubbles);
+ mKeyguardBypassController.listenForQsExpandedChange();
mStatusBarSignalPolicy.init();
mKeyguardIndicationController.init();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a8941bb182ae..97791acfb43a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,7 +21,6 @@ import android.content.pm.PackageManager
import android.content.res.Resources
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
-import androidx.annotation.VisibleForTesting
import com.android.app.tracing.ListenersTracing.forEachTraced
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -32,7 +31,7 @@ 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.res.R
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -41,23 +40,24 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
+import dagger.Lazy
+import java.io.PrintWriter
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import javax.inject.Inject
@SysUISingleton
class KeyguardBypassController @Inject constructor(
@Main resources: Resources,
packageManager: PackageManager,
- @Application applicationScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
tunerService: TunerService,
private val statusBarStateController: StatusBarStateController,
lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardStateController: KeyguardStateController,
- private val shadeRepository: ShadeRepository,
+ private val shadeInteractorLazy: Lazy<ShadeInteractor>,
devicePostureController: DevicePostureController,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
dumpManager: DumpManager
@@ -144,7 +144,6 @@ class KeyguardBypassController @Inject constructor(
}
}
})
- listenForQsExpandedChange(applicationScope)
val dismissByDefault = if (resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
tunerService.addTunable({ key, _ ->
@@ -159,10 +158,9 @@ class KeyguardBypassController @Inject constructor(
}
}
- @VisibleForTesting
- fun listenForQsExpandedChange(scope: CoroutineScope) =
- scope.launch {
- shadeRepository.qsExpansion.map { it > 0f }.distinctUntilChanged()
+ fun listenForQsExpandedChange() =
+ applicationScope.launch {
+ shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
qsExpanded = isQsExpanded
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 68d54e73774e..6b685118a462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -131,7 +131,12 @@ constructor(
val runnable = Runnable {
assistManagerLazy.get().hideAssist()
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ 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(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a20468f9e3ba..ec88b6c56477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -98,7 +98,7 @@ class OngoingCallController @Inject constructor(
(entry.sbn.key == callNotificationInfo?.key)) {
val newOngoingCallInfo = CallNotificationInfo(
entry.sbn.key,
- entry.sbn.notification.`when`,
+ entry.sbn.notification.getWhen(),
entry.sbn.notification.contentIntent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index f2255f3248e0..332c1210f8cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -126,10 +126,9 @@ constructor(
) { shouldShow, connectionState ->
if (shouldShow) {
when (connectionState) {
+ SatelliteConnectionState.On,
SatelliteConnectionState.Connected ->
context.getString(R.string.satellite_connected_carrier_text)
- SatelliteConnectionState.On ->
- context.getString(R.string.satellite_not_connected_carrier_text)
SatelliteConnectionState.Off,
SatelliteConnectionState.Unknown -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e6556645afb..b3ea9dcb7cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -6,8 +6,8 @@ import android.os.VibrationEffect
import android.os.Vibrator
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldProvider
-import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -18,6 +18,7 @@ class UnfoldHapticsPlayer
constructor(
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
foldProvider: FoldProvider,
+ transitionConfig: UnfoldTransitionConfig,
@Main private val mainExecutor: Executor,
private val vibrator: Vibrator?
) : TransitionProgressListener {
@@ -27,22 +28,17 @@ constructor(
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
init {
- if (vibrator != null) {
+ if (vibrator != null && transitionConfig.isHapticsEnabled) {
// We don't need to remove the callback because we should listen to it
// the whole time when SystemUI process is alive
unfoldTransitionProgressProvider.addCallback(this)
- }
- foldProvider.registerCallback(
- object : FoldCallback {
- override fun onFoldUpdated(isFolded: Boolean) {
- if (isFolded) {
- isFirstAnimationAfterUnfold = true
- }
+ foldProvider.registerCallback({ isFolded ->
+ if (isFolded) {
+ isFirstAnimationAfterUnfold = true
}
- },
- mainExecutor
- )
+ }, mainExecutor)
+ }
}
private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 0dcbe9b2bfc4..229bdce62cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,8 +26,9 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.annotations.WeaklyReferencedCallback;
@@ -118,7 +119,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
private final Intent mServiceIntent;
private final UserTracker mUserTracker;
private final int mFlags;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
private final ServiceTransformer<T> mTransformer;
private final ArrayList<WeakReference<Callback<T>>> mCallbacks;
private Optional<Integer> mLastDisconnectReason;
@@ -130,30 +131,34 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
* Default constructor for {@link ObservableServiceConnection}.
* @param context The context from which the service will be bound with.
* @param serviceIntent The intent to bind service with.
- * @param executor The executor for connection callbacks to be delivered on
+ * @param bgExecutor The executor for connection callbacks to be delivered on
* @param transformer A {@link ServiceTransformer} for transforming the resulting service
* into a desired type.
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
UserTracker userTracker,
- @Main Executor executor,
+ @Background Executor bgExecutor,
ServiceTransformer<T> transformer) {
mContext = context;
mServiceIntent = serviceIntent;
mUserTracker = userTracker;
mFlags = Context.BIND_AUTO_CREATE;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
mTransformer = transformer;
mCallbacks = new ArrayList<>();
mLastDisconnectReason = Optional.empty();
}
/**
- * Initiate binding to the service.
- * @return {@code true} if initiating binding succeed, {@code false} otherwise.
+ * Initiate binding to the service in the background.
*/
- public boolean bind() {
+ public void bind() {
+ mBgExecutor.execute(this::bindInternal);
+ }
+
+ @WorkerThread
+ private void bindInternal() {
boolean bindResult = false;
try {
bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags,
@@ -166,18 +171,17 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
if (DEBUG) {
Log.d(TAG, "bind. bound:" + bindResult);
}
- return bindResult;
}
/**
* Disconnect from the service if bound.
*/
public void unbind() {
- onDisconnected(DISCONNECT_REASON_UNBIND);
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_UNBIND));
}
/**
- * Adds a callback for receiving connection updates.
+ * Adds a callback for receiving connection updates. The callback is executed in the background.
* @param callback The {@link Callback} to receive future updates.
*/
public void addCallback(Callback<T> callback) {
@@ -185,7 +189,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
Log.d(TAG, "addCallback:" + callback);
}
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
@@ -210,14 +214,15 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
* Removes previously added callback from receiving future connection updates.
* @param callback The {@link Callback} to be removed.
*/
- public void removeCallback(Callback callback) {
+ public void removeCallback(Callback<T> callback) {
if (DEBUG) {
Log.d(TAG, "removeCallback:" + callback);
}
- mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
+ mBgExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
}
+ @WorkerThread
private void onDisconnected(@DisconnectReason int reason) {
if (DEBUG) {
Log.d(TAG, "onDisconnected:" + reason);
@@ -240,7 +245,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
if (DEBUG) {
Log.d(TAG, "onServiceConnected");
}
@@ -268,7 +273,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
+ final Callback<T> cb = iterator.next().get();
if (cb != null) {
applicator.accept(cb);
} else {
@@ -279,16 +284,16 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
@Override
public void onServiceDisconnected(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
}
@Override
public void onBindingDied(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
}
@Override
public void onNullBinding(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 5979f3e60cb9..64f8246118c8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -48,7 +48,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
- private final DelayableExecutor mMainExecutor;
+ private final DelayableExecutor mBgExecutor;
private final int mBaseReconnectDelayMs;
private final int mMaxReconnectAttempts;
private final int mMinConnectionDuration;
@@ -71,8 +71,8 @@ public class PersistentConnectionManager<T> implements Dumpable {
private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
- private final ObservableServiceConnection.Callback mConnectionCallback =
- new ObservableServiceConnection.Callback() {
+ private final ObservableServiceConnection.Callback<T> mConnectionCallback =
+ new ObservableServiceConnection.Callback<>() {
private long mStartTime;
@Override
@@ -95,12 +95,10 @@ public class PersistentConnectionManager<T> implements Dumpable {
}
};
- // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
- // qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
@Inject
public PersistentConnectionManager(
SystemClock clock,
- @Background DelayableExecutor mainExecutor,
+ @Background DelayableExecutor bgExecutor,
DumpManager dumpManager,
@Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@@ -109,7 +107,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
@Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
@Named(OBSERVER) Observer observer) {
mSystemClock = clock;
- mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
mConnection = serviceConnection;
mObserver = observer;
mDumpManager = dumpManager;
@@ -195,7 +193,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
}
- mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+ mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
mReconnectAttempts++;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
index f11d5d18ac84..0968bde4d284 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
@@ -21,26 +21,29 @@ import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.text.TextUtils
-import android.util.Log
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor
+import com.android.systemui.volume.ui.navigation.VolumeNavigator
import javax.inject.Inject
-private const val TAG = "VolumePanelDialogReceiver"
-private const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
-private const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
-
-/**
- * BroadcastReceiver for handling volume panel dialog intent
- */
-class VolumePanelDialogReceiver @Inject constructor(
- private val volumePanelFactory: VolumePanelFactory
+/** [BroadcastReceiver] for handling volume panel dialog intent */
+class VolumePanelDialogReceiver
+@Inject
+constructor(
+ private val volumeNavigator: VolumeNavigator,
+ private val volumePanelNavigationInteractor: VolumePanelNavigationInteractor,
) : BroadcastReceiver() {
+
override fun onReceive(context: Context, intent: Intent) {
- Log.d(TAG, "onReceive intent" + intent.action)
- if (TextUtils.equals(LAUNCH_ACTION, intent.action) ||
- TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)) {
- volumePanelFactory.create(true, null)
- } else if (TextUtils.equals(DISMISS_ACTION, intent.action)) {
- volumePanelFactory.dismiss()
+ if (
+ TextUtils.equals(LAUNCH_ACTION, intent.action) ||
+ TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)
+ ) {
+ volumeNavigator.openVolumePanel(volumePanelNavigationInteractor.getVolumePanelRoute())
}
}
+
+ companion object {
+ const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
+ const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index fb92a0fcd4c5..dc1e8cf2ea01 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
@@ -37,6 +38,7 @@ import com.android.systemui.volume.CsdWarningDialog;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
+import com.android.systemui.volume.VolumePanelDialogReceiver;
import com.android.systemui.volume.VolumeUI;
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
@@ -65,6 +67,15 @@ import dagger.multibindings.IntoSet;
}
)
public interface VolumeModule {
+
+ /**
+ * Binds [VolumePanelDialogReceiver]
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumePanelDialogReceiver.class)
+ BroadcastReceiver bindVolumePanelDialogReceiver(VolumePanelDialogReceiver receiver);
+
/** Starts VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index ee642a64242d..038633810fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
import android.media.AudioManager
+import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
@@ -144,6 +145,7 @@ constructor(
if (isMutedOrNoVolume) {
when (audioStream.value) {
AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
AudioManager.STREAM_RING ->
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -158,12 +160,18 @@ constructor(
R.drawable.ic_volume_off
}
AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
- else -> null
+ else -> {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_volume_off
+ }
}
} else {
iconsByStream[audioStream]
+ ?: run {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_music_note
+ }
}
- ?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
@@ -196,4 +204,8 @@ constructor(
* when using [AudioStream] directly because it expects another type.
*/
class FactoryAudioStreamWrapper(val audioStream: AudioStream)
+
+ private companion object {
+ const val TAG = "AudioStreamSliderViewModel"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index eff408a5cbdb..a30de1b89695 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -17,11 +17,14 @@
package com.android.systemui.volume.panel.ui.viewmodel
import android.content.Context
+import android.content.IntentFilter
import android.content.res.Resources
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.VolumePanelDialogReceiver
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.VolumePanelStartable
@@ -37,6 +40,8 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -49,6 +54,7 @@ class VolumePanelViewModel(
coroutineScope: CoroutineScope,
daggerComponentFactory: VolumePanelComponentFactory,
configurationController: ConfigurationController,
+ broadcastDispatcher: BroadcastDispatcher,
) {
private val volumePanelComponent: VolumePanelComponent =
@@ -113,6 +119,10 @@ class VolumePanelViewModel(
init {
volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION))
+ .onEach { dismissPanel() }
+ .launchIn(scope)
}
fun dismissPanel() {
@@ -125,6 +135,7 @@ class VolumePanelViewModel(
@Application private val context: Context,
private val daggerComponentFactory: VolumePanelComponentFactory,
private val configurationController: ConfigurationController,
+ private val broadcastDispatcher: BroadcastDispatcher,
) {
fun create(coroutineScope: CoroutineScope): VolumePanelViewModel {
@@ -133,6 +144,7 @@ class VolumePanelViewModel(
coroutineScope,
daggerComponentFactory,
configurationController,
+ broadcastDispatcher
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 4684b805ceac..79e312fec5f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -27,9 +27,11 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
@@ -229,6 +231,11 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
givenOnOccludingApp(true)
givenFingerprintAllowed(true)
keyguardRepository.setIsDozing(true)
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.DOZING,
+ testScope
+ )
runCurrent()
// ERROR message
@@ -254,7 +261,7 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
assertThat(message).isNull()
}
- private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ private suspend fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
powerRepository.setInteractive(true)
keyguardRepository.setIsDozing(false)
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
@@ -262,6 +269,20 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
keyguardRepository.setDreaming(false)
bouncerRepository.setPrimaryShow(!isOnOccludingApp)
bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+
+ if (isOnOccludingApp) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ } else {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ testScope
+ )
+ }
}
private fun givenFingerprintAllowed(allowed: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 96b759682cfe..35659c12fad5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
@@ -83,6 +84,7 @@ import org.mockito.MockitoAnnotations
)
@SmallTest
@RunWith(Parameterized::class)
+@DisableSceneContainer
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
new file mode 100644
index 000000000000..ef3183a8891f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -0,0 +1,406 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@FlakyTest(
+ bugId = 292574995,
+ detail = "on certain architectures all permutations with startActivity=true is causing failures"
+)
+@SmallTest
+@RunWith(Parameterized::class)
+@EnableSceneContainer
+class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+ private val DRAWABLE =
+ mock<Icon> {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ )
+
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller
+ @Mock private lateinit var expandable: Expandable
+ @Mock private lateinit var launchAnimator: DialogTransitionAnimator
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
+ private lateinit var testScope: TestScope
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+ @JvmField @Parameter(4) var startActivity: Boolean = false
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var dockManager: DockManagerFake
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var userTracker: UserTracker
+
+ private val kosmos = testKosmos()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(expandable.activityTransitionController()).thenReturn(animationController)
+
+ userTracker = FakeUserTracker()
+ homeControls =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+ dockManager = DockManagerFake()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ val quickAccessWallet =
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ val qrCodeScanner =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val featureFlags = FakeFeatureFlags()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ underTest =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(
+ featureFlags = featureFlags,
+ )
+ .keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags = featureFlags,
+ repository = { quickAffordanceRepository },
+ launchAnimator = launchAnimator,
+ logger = logger,
+ devicePolicyManager = devicePolicyManager,
+ dockManager = dockManager,
+ biometricSettingsRepository = biometricSettingsRepository,
+ backgroundDispatcher = testDispatcher,
+ appContext = mContext,
+ sceneInteractor = { kosmos.sceneInteractor },
+ )
+ }
+
+ @Test
+ fun onQuickAffordanceTriggered() =
+ testScope.runTest {
+ val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ homeControls.setState(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = DRAWABLE,
+ )
+ )
+ homeControls.onTriggeredResult =
+ if (startActivity) {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ underTest.onQuickAffordanceTriggered(
+ configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::$key",
+ expandable = expandable,
+ slotId = "",
+ )
+
+ if (startActivity) {
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ any(),
+ /* delay= */ eq(0),
+ same(animationController),
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ any(),
+ /* dismissShade= */ eq(true),
+ same(animationController),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ )
+ }
+ } else {
+ verifyZeroInteractions(activityStarter)
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
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 6b317ead2bfa..1881a9e0c205 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
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +33,7 @@ import com.android.systemui.dock.DockManagerFake
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
@@ -75,17 +77,18 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -111,6 +114,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -750,5 +757,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 1d98dc3a8f47..0c98cff89ee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -18,11 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
@@ -45,14 +46,14 @@ import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(JUnit4::class)
-@DisableSceneContainer
-class KeyguardClockViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val underTest = kosmos.keyguardClockViewModel
@@ -65,6 +66,10 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
var config = ClockConfig("TEST", "Test", "")
var faceConfig = ClockFaceConfig()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -276,5 +281,11 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
companion object {
private const val KEYGUARD_STATUS_BAR_HEIGHT = 20
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index 72fc65b7c8d4..924b872546c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -2,6 +2,8 @@ package com.android.systemui.screenshot
import android.graphics.drawable.Drawable
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.View
import android.view.ViewGroup
@@ -10,10 +12,16 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Guideline
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.message.LabeledIcon
+import com.android.systemui.screenshot.message.ProfileMessageController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,6 +36,7 @@ class MessageContainerControllerTest : SysuiTestCase() {
lateinit var messageContainer: MessageContainerController
@Mock lateinit var workProfileMessageController: WorkProfileMessageController
+ @Mock lateinit var profileMessageController: ProfileMessageController
@Mock lateinit var screenshotDetectionController: ScreenshotDetectionController
@@ -46,12 +55,15 @@ class MessageContainerControllerTest : SysuiTestCase() {
lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
@Before
+ @ExperimentalCoroutinesApi
fun setup() {
MockitoAnnotations.initMocks(this)
messageContainer =
MessageContainerController(
workProfileMessageController,
+ profileMessageController,
screenshotDetectionController,
+ TestScope(UnconfinedTestDispatcher())
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -78,20 +90,11 @@ class MessageContainerControllerTest : SysuiTestCase() {
}
@Test
- fun testOnScreenshotTakenUserHandle_noWorkProfileFirstRun() {
- // (just being explicit here)
- whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(null)
-
- messageContainer.onScreenshotTaken(userHandle)
-
- verify(workProfileMessageController, never()).populateView(any(), any(), any())
- }
-
- @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
.thenReturn(workProfileData)
- messageContainer.onScreenshotTaken(userHandle)
+ messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController)
.populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
@@ -100,10 +103,28 @@ class MessageContainerControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
+ fun testOnScreenshotTakenUserHandle_withProfileProfileFirstRun() = runTest {
+ val profileData =
+ ProfileMessageController.ProfileFirstRunData(
+ LabeledIcon(appName, icon),
+ ProfileMessageController.FirstRunProfile.PRIVATE
+ )
+ whenever(profileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(profileData)
+ messageContainer.onScreenshotTaken(screenshotData)
+
+ verify(profileMessageController)
+ .bindView(eq(workProfileFirstRunView), eq(profileData), any())
+ assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
+ assertEquals(View.GONE, detectionNoticeView.visibility)
+ }
+
+ @Test
fun testOnScreenshotTakenScreenshotData_nothingToShow() {
messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController, never()).populateView(any(), any(), any())
+ verify(profileMessageController, never()).bindView(any(), any(), any())
verify(screenshotDetectionController, never()).populateView(any(), any())
assertEquals(View.GONE, container.visibility)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 4b7d5f0fff95..ebdc49f75558 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -87,7 +87,7 @@ public class WorkProfileMessageControllerTest extends SysuiTestCase {
when(mMockContext.getSharedPreferences(
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
- when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ when(mMockContext.getString(R.string.config_screenshotFilesApp))
.thenReturn(FILES_APP_COMPONENT);
when(mMockContext.getString(R.string.screenshot_default_files_app_name))
.thenReturn(DEFAULT_FILES_APP_LABEL);
@@ -149,7 +149,7 @@ public class WorkProfileMessageControllerTest extends SysuiTestCase {
@Test
public void testOnScreenshotTaken_noFilesAppComponentDefined() {
- when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ when(mMockContext.getString(R.string.config_screenshotFilesApp))
.thenReturn("");
WorkProfileMessageController.WorkProfileFirstRunData data =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
new file mode 100644
index 000000000000..ce2facd59482
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.screenshot.message
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class ProfileMessageControllerTest {
+ @Test
+ fun personalScreenshot() = runTest {
+ assertThat(
+ getMessageController()
+ .onScreenshotTaken(UserHandle.of(profileTypeRepository.personalUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun communalScreenshot() = runTest {
+ assertThat(
+ getMessageController()
+ .onScreenshotTaken(UserHandle.of(profileTypeRepository.communalUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun noUserScreenshot() = runTest {
+ assertThat(getMessageController().onScreenshotTaken(null)).isNull()
+ }
+
+ @Test
+ fun alreadyDismissed() = runTest {
+ val messageController = getMessageController()
+ profileFirstRunSettings.onMessageDismissed(ProfileMessageController.FirstRunProfile.WORK)
+ assertThat(
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.workUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun noFileManager() = runTest {
+ val messageController = getMessageController(fileManagerComponent = null)
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.workUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.WORK)
+ assertThat(data?.labeledIcon?.label).isEqualTo(DEFAULT_APP_NAME)
+ assertThat(data?.labeledIcon?.badgedIcon).isNull()
+ }
+
+ @Test
+ fun fileManagerNotFound() = runTest {
+ val messageController =
+ getMessageController(fileManagerComponent = ComponentName("Something", "Random"))
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.privateUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.PRIVATE)
+ assertThat(data?.labeledIcon?.label).isEqualTo(DEFAULT_APP_NAME)
+ assertThat(data?.labeledIcon?.badgedIcon).isNull()
+ }
+
+ @Test
+ fun fileManagerFound() = runTest {
+ val messageController = getMessageController()
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.privateUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.PRIVATE)
+ assertThat(data?.labeledIcon?.label).isEqualTo(FILE_MANAGER_LABEL)
+ assertThat(data?.labeledIcon?.badgedIcon).isEqualTo(drawable)
+ }
+
+ private val drawable =
+ object : Drawable() {
+ override fun draw(canvas: Canvas) {}
+
+ override fun setAlpha(alpha: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+ override fun getOpacity(): Int = 0
+ }
+
+ private val packageLabelIconProvider =
+ object : PackageLabelIconProvider {
+ override suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon {
+ if (componentName.equals(FILE_MANAGER_COMPONENT)) {
+ return LabeledIcon(FILE_MANAGER_LABEL, drawable)
+ } else {
+ throw PackageManager.NameNotFoundException()
+ }
+ }
+ }
+
+ private class FakeProfileFirstRunResources(private val fileManager: ComponentName?) :
+ ProfileFirstRunFileResources {
+ override fun fileManagerComponentName(): ComponentName? {
+ return fileManager
+ }
+
+ override fun defaultFileApp() = LabeledIcon(DEFAULT_APP_NAME, badgedIcon = null)
+ }
+
+ private val profileFirstRunSettings =
+ object : ProfileFirstRunSettings {
+ private val dismissed =
+ mutableMapOf(
+ ProfileMessageController.FirstRunProfile.WORK to false,
+ ProfileMessageController.FirstRunProfile.PRIVATE to false,
+ )
+
+ override fun messageAlreadyDismissed(
+ profileType: ProfileMessageController.FirstRunProfile
+ ): Boolean {
+ return dismissed.getOrDefault(profileType, false)
+ }
+
+ override fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile) {
+ dismissed[profileType] = true
+ }
+ }
+
+ private val profileTypeRepository =
+ object : ProfileTypeRepository {
+ override suspend fun getProfileType(userId: Int): ProfileType {
+ return when (userId) {
+ workUser -> ProfileType.WORK
+ privateUser -> ProfileType.PRIVATE
+ communalUser -> ProfileType.COMMUNAL
+ else -> ProfileType.NONE
+ }
+ }
+
+ val personalUser = 0
+ val workUser = 1
+ val privateUser = 2
+ val communalUser = 3
+ }
+
+ private fun getMessageController(
+ fileManagerComponent: ComponentName? = FILE_MANAGER_COMPONENT
+ ) =
+ ProfileMessageController(
+ packageLabelIconProvider,
+ FakeProfileFirstRunResources(fileManagerComponent),
+ profileFirstRunSettings,
+ profileTypeRepository
+ )
+
+ companion object {
+ val FILE_MANAGER_COMPONENT = ComponentName("package", "component")
+ const val DEFAULT_APP_NAME = "default app"
+ const val FILE_MANAGER_LABEL = "file manager"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 0a9bac91004a..7ade053720e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -49,6 +49,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -58,6 +59,7 @@ import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -469,6 +471,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
ensureStateForHeadsUpWhenAwake();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 4dd97bc90546..9b4f9312a7ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,19 +17,21 @@
package com.android.systemui.statusbar.phone
import android.content.pm.PackageManager
-import android.testing.AndroidTestingRunner
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
@@ -58,15 +60,17 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
- private val shadeRepository = FakeShadeRepository()
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private lateinit var keyguardBypassController: KeyguardBypassController
private lateinit var postureControllerCallback: DevicePostureController.Callback
@@ -79,6 +83,18 @@ class KeyguardBypassControllerTest : SysuiTestCase() {
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var packageManager: PackageManager
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Captor
private val postureCallbackCaptor =
ArgumentCaptor.forClass(DevicePostureController.Callback::class.java)
@@ -148,7 +164,7 @@ class KeyguardBypassControllerTest : SysuiTestCase() {
statusBarStateController,
lockscreenUserManager,
keyguardStateController,
- shadeRepository,
+ { kosmos.shadeInteractor },
devicePostureController,
keyguardTransitionInteractor,
dumpManager,
@@ -293,13 +309,13 @@ class KeyguardBypassControllerTest : SysuiTestCase() {
testScope.runTest {
initKeyguardBypassController()
assertThat(keyguardBypassController.qsExpanded).isFalse()
- val job = keyguardBypassController.listenForQsExpandedChange(this)
- shadeRepository.setQsExpansion(0.5f)
+ val job = keyguardBypassController.listenForQsExpandedChange()
+ shadeTestUtil.setQsExpansion(0.5f)
runCurrent()
assertThat(keyguardBypassController.qsExpanded).isTrue()
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
runCurrent()
assertThat(keyguardBypassController.qsExpanded).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index d365663e3621..ba38f871cf9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -314,6 +314,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
private class UnfoldConfig : UnfoldTransitionConfig {
override var isEnabled: Boolean = false
override var isHingeAngleEnabled: Boolean = false
+ override val isHapticsEnabled: Boolean = false
override val halfFoldedTimeoutMillis: Int = 0
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index efd7f9969d49..05464f3b715a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -23,6 +23,7 @@ import android.app.Notification
import android.app.PendingIntent
import android.app.Person
import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.notification.NotificationListenerService.REASON_USER_STOPPED
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -194,7 +195,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
/** Regression test for b/192379214. */
@Test
- @DisableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME)
+ @DisableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
fun onEntryUpdated_notificationWhenIsZero_timeHidden() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
notification.modifyNotification(context).setWhen(0)
@@ -210,6 +211,22 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
+ fun onEntryUpdated_notificationWhenIsZero_timeShown() {
+ val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
+ notification.modifyNotification(context).setWhen(0)
+
+ notifCollectionListener.onEntryUpdated(notification.build())
+ chipView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ )
+
+ assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ .isGreaterThan(0)
+ }
+
+ @Test
fun onEntryUpdated_notificationWhenIsValid_timeShown() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
notification.modifyNotification(context).setWhen(clock.currentTimeMillis())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 8eea29beb6dd..ceaae9e02e87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -587,7 +587,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
advanceTimeBy(10.seconds)
assertThat(latest)
- .isEqualTo(context.getString(R.string.satellite_not_connected_carrier_text))
+ .isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
}
@OptIn(ExperimentalCoroutinesApi::class)
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 bdd3d188ad91..cfa734a14811 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
@@ -16,71 +16,69 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
-import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
-import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class KeyguardStatusBarViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardInteractor =
- KeyguardInteractor(
- keyguardRepository,
- mock<CommandQueue>(),
- PowerInteractorFactory.create().powerInteractor,
- FakeKeyguardBouncerRepository(),
- ConfigurationInteractor(FakeConfigurationRepository()),
- FakeShadeRepository(),
- kosmos.keyguardTransitionInteractor,
- { kosmos.sceneInteractor },
- { kosmos.fromGoneTransitionInteractor },
- { kosmos.sharedNotificationContainerInteractor },
- testScope,
- )
- private val keyguardStatusBarInteractor =
- KeyguardStatusBarInteractor(
- FakeKeyguardStatusBarRepository(),
- )
- private val batteryController = mock<BatteryController>()
-
- private val underTest =
- KeyguardStatusBarViewModel(
- testScope.backgroundScope,
- keyguardInteractor,
- keyguardStatusBarInteractor,
- batteryController,
- )
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val keyguardStatusBarInteractor by lazy { kosmos.keyguardStatusBarInteractor }
+ private val batteryController = kosmos.batteryController
+
+ lateinit var underTest: KeyguardStatusBarViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest =
+ KeyguardStatusBarViewModel(
+ testScope.backgroundScope,
+ keyguardInteractor,
+ keyguardStatusBarInteractor,
+ batteryController,
+ )
+ }
@Test
fun isVisible_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index fd513c9c9235..06f1a8848fad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -17,13 +17,12 @@ package com.android.systemui.unfold
import android.os.VibrationAttributes
import android.os.VibrationEffect
-import android.os.Vibrator
+import android.os.vibrator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.util.TestFoldProvider
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,15 +34,20 @@ import org.mockito.Mockito.verify
@SmallTest
class UnfoldHapticsPlayerTest : SysuiTestCase() {
- private val progressProvider = FakeUnfoldTransitionProvider()
- private val vibrator: Vibrator = mock()
- private val testFoldProvider = TestFoldProvider()
+ private val kosmos = testKosmos()
+
+ private val progressProvider = kosmos.fakeUnfoldTransitionProgressProvider
+ private val vibrator = kosmos.vibrator
+ private val transitionConfig = kosmos.unfoldTransitionConfig
+ private val testFoldProvider = kosmos.foldProvider
private lateinit var player: UnfoldHapticsPlayer
@Before
fun before() {
- player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, Runnable::run, vibrator)
+ transitionConfig.isHapticsEnabled = true
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, transitionConfig,
+ Runnable::run, vibrator)
}
@Test
@@ -58,6 +62,21 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() {
}
@Test
+ fun testHapticsDisabled_unfoldingTransitionFinishing_doesNotPlayHaptics() {
+ transitionConfig.isHapticsEnabled = false
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, transitionConfig,
+ Runnable::run, vibrator)
+
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+
+ verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
+ }
+
+ @Test
fun testUnfoldingTransitionFinishingLate_doesNotPlayHaptics() {
testFoldProvider.onFoldUpdate(isFolded = true)
testFoldProvider.onFoldUpdate(isFolded = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
new file mode 100644
index 000000000000..c073903bc005
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.unfold
+
+import com.android.systemui.unfold.util.TestFoldProvider
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.unfold.config.TestUnfoldTransitionConfig
+
+var Kosmos.foldProvider: TestFoldProvider by Kosmos.Fixture { TestFoldProvider() }
+
+var Kosmos.unfoldTransitionConfig: TestUnfoldTransitionConfig
+ by Kosmos.Fixture { TestUnfoldTransitionConfig() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
index ab450e2506f9..3c539979c527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
@@ -47,6 +47,12 @@ class ResourceUnfoldTransitionConfigTest : SysuiTestCase() {
}
@Test
+ fun testHapticsEnabled() {
+ assertThat(config.isHapticsEnabled).isEqualTo(mContext.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHapticsEnabled))
+ }
+
+ @Test
fun testHalfFoldedTimeout() {
assertThat(config.halfFoldedTimeoutMillis).isEqualTo(mContext.resources
.getInteger(com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
new file mode 100644
index 000000000000..058611507280
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.unfold.config
+
+class TestUnfoldTransitionConfig(
+ override var isEnabled: Boolean = false,
+ override var isHingeAngleEnabled: Boolean = false,
+ override var isHapticsEnabled: Boolean = false,
+ override var halfFoldedTimeoutMillis: Int = 1000
+) : UnfoldTransitionConfig
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 5d341207ef6a..8d26c877f4cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.util.service;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -118,6 +116,7 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
connection.addCallback(mCallback);
mExecutor.runAllReady();
connection.bind();
+ mExecutor.runAllReady();
when(mTransformer.convert(eq(mBinder))).thenReturn(mResult);
@@ -143,8 +142,8 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
connection.bind();
+ mExecutor.runAllReady();
connection.onServiceDisconnected(mComponentName);
-
mExecutor.runAllReady();
// Ensure proper disconnect reason reported back
@@ -157,6 +156,7 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
clearInvocations(mContext);
// Ensure unbind after disconnect has no effect on the connection
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
@@ -197,7 +197,8 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
// Verify that the exception was caught and that bind returns false, and we properly
// unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
}
@@ -212,13 +213,15 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
.thenThrow(new SecurityException());
// Verify that bind returns false and we properly unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
clearInvocations(mContext);
// Ensure unbind after the failed bind has no effect.
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 2017954c6b34..56e5e293c799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,10 +77,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
import android.util.SparseArray;
@@ -99,46 +99,28 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.LargeScreenHeaderHelper;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeWindowLogger;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -154,7 +136,6 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -163,13 +144,10 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.SystemSettings;
@@ -207,8 +185,6 @@ import com.android.wm.shell.taskview.TaskView;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -227,8 +203,11 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblesTest extends SysuiTestCase {
@Mock
@@ -355,11 +334,8 @@ public class BubblesTest extends SysuiTestCase {
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
- @Mock
- private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -376,8 +352,16 @@ public class BubblesTest extends SysuiTestCase {
private UserHandle mUser0;
private FakeBubbleProperties mBubbleProperties;
- private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
- private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ }
+
+ public BubblesTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
@@ -402,77 +386,14 @@ public class BubblesTest extends SysuiTestCase {
FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
+ mKosmos.getFakeDeviceProvisioningRepository();
deviceProvisioningRepository.setDeviceProvisioned(true);
- FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
- FakeShadeRepository shadeRepository = new FakeShadeRepository();
- FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
-
- PowerInteractor powerInteractor = new PowerInteractor(
- mKosmos.getPowerRepository(),
- mKosmos.getFalsingCollector(),
- mock(ScreenOffAnimationController.class),
- mStatusBarStateController);
-
- SceneInteractor sceneInteractor = new SceneInteractor(
- mTestScope.getBackgroundScope(),
- new SceneContainerRepository(
- mTestScope.getBackgroundScope(),
- mKosmos.getFakeSceneContainerConfig(),
- mKosmos.getSceneDataSource()),
- mock(SceneLogger.class),
- mKosmos.getDeviceUnlockedInteractor());
-
- KeyguardTransitionInteractor keyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
- keyguardRepository,
- new FakeCommandQueue(),
- powerInteractor,
- new FakeKeyguardBouncerRepository(),
- new ConfigurationInteractor(configurationRepository),
- shadeRepository,
- keyguardTransitionInteractor,
- () -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor(),
- () -> mKosmos.getSharedNotificationContainerInteractor(),
- mTestScope);
-
- mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
- mFromPrimaryBouncerTransitionInteractor =
- mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
- ResourcesSplitShadeStateController splitShadeStateController =
- new ResourcesSplitShadeStateController();
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
- mShadeInteractor =
- new ShadeInteractorImpl(
- mTestScope.getBackgroundScope(),
- mKosmos.getDeviceProvisioningInteractor(),
- new FakeDisableFlagsRepository(),
- mDozeParameters,
- keyguardRepository,
- keyguardTransitionInteractor,
- powerInteractor,
- new FakeUserSetupRepository(),
- mock(UserSwitcherInteractor.class),
- new ShadeInteractorLegacyImpl(
- mTestScope.getBackgroundScope(), keyguardRepository,
- new SharedNotificationContainerInteractor(
- configurationRepository,
- mContext,
- splitShadeStateController,
- keyguardInteractor,
- deviceEntryUdfpsInteractor,
- () -> mLargeScreenHeaderHelper),
- shadeRepository
- )
- );
+ mShadeInteractor = mKosmos.getShadeInteractor();
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
@@ -2467,6 +2388,10 @@ public class BubblesTest extends SysuiTestCase {
mStateChangeCalls++;
mLastUpdate = update;
}
+
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ }
}
private static class FakeBubbleProperties implements BubbleProperties {
diff --git a/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt
new file mode 100644
index 000000000000..872b25c81835
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.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 android.os
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.vibrator by Kosmos.Fixture { mock<Vibrator>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 4221d06e27ed..297d1d8c2c0a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.util.mockito.mock
val Kosmos.shadeLockscreenInteractor by
@@ -28,5 +29,6 @@ val Kosmos.shadeLockscreenInteractor by
shadeInteractor = shadeInteractorImpl,
sceneInteractor = sceneInteractor,
lockIconViewController = mock(),
+ shadeRepository = shadeRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt
new file mode 100644
index 000000000000..da95ee90dade
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardStatusBarRepository: FakeKeyguardStatusBarRepository by
+ Kosmos.Fixture { fakeKeyguardStatusBarRepository }
+
+val Kosmos.fakeKeyguardStatusBarRepository: FakeKeyguardStatusBarRepository by
+ Kosmos.Fixture { FakeKeyguardStatusBarRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt
new file mode 100644
index 000000000000..71ed5f696906
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.keyguardStatusBarRepository
+
+val Kosmos.keyguardStatusBarInteractor: KeyguardStatusBarInteractor by
+ Kosmos.Fixture {
+ KeyguardStatusBarInteractor(
+ keyguardStatusBarRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index cbba80baee7f..d00eedf22efc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
@@ -60,6 +61,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
shadeInteractor = shadeInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
index 4a794523a86b..146f1093fe69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel
import android.content.res.mainResources
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
@@ -72,5 +73,6 @@ var Kosmos.volumePanelViewModel: VolumePanelViewModel by
testScope.backgroundScope,
KosmosVolumePanelComponentFactory(this),
fakeConfigurationController,
+ broadcastDispatcher,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
index c51372975a67..ca1daf66f581 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -33,6 +33,12 @@ class ResourceUnfoldTransitionConfig @Inject constructor() : UnfoldTransitionCon
Resources.getSystem().getBoolean(id)
}
+ override val isHapticsEnabled: Boolean by lazy {
+ val id = Resources.getSystem()
+ .getIdentifier("config_unfoldTransitionHapticsEnabled", "bool", "android")
+ Resources.getSystem().getBoolean(id)
+ }
+
override val halfFoldedTimeoutMillis: Int by lazy {
val id = Resources.getSystem()
.getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android")
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
index 765e862aa00d..1084cb3f234b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
@@ -18,5 +18,6 @@ package com.android.systemui.unfold.config
interface UnfoldTransitionConfig {
val isEnabled: Boolean
val isHingeAngleEnabled: Boolean
+ val isHapticsEnabled: Boolean
val halfFoldedTimeoutMillis: Int
}
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index e77f846ffaa6..f6885e1e74ba 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,11 +1,18 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
+ { "name": "tiny-framework-dump-test" },
+ { "name": "hoststubgentest" },
+ { "name": "hoststubgen-invoke-test" },
{
"name": "RavenwoodMockitoTest_device"
},
{
"name": "RavenwoodBivalentTest_device"
},
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
"options": [
@@ -18,6 +25,19 @@
]
}
],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
"ravenwood-presubmit": [
{
"name": "RavenwoodMinimumTest",
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index b5843d0fa26b..beacde282d49 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -39,14 +39,18 @@ dump() {
local jar=$1
local file=$2
- sed -e '1d' -e "s/^/$jar,/" $file
+ # Use sed to remove the header + prepend the jar filename.
+ sed -e '1d' -e "s/^/$jar,/" $file
}
collect_stats() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
- dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_stats.csv
+
+ dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
dump "service.core" hoststubgen_services.core_stats.csv
} > "$out"
@@ -56,7 +60,10 @@ collect_stats() {
collect_apis() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_apis.csv
+
dump "framework-minus-apex" hoststubgen_framework-minus-apex_apis.csv
dump "service.core" hoststubgen_services.core_apis.csv
} > "$out"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 300b147509a7..8ee560b811aa 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -256,6 +256,7 @@ java_library_static {
"stats_flags_lib",
"core_os_flags_lib",
"connectivity_flags_lib",
+ "dreams_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 23891d23cf4f..ec0d8974603e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3864,10 +3864,12 @@ public final class ActiveServices {
final long lastTopTime = sr.app.mState.getLastTopTime();
final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
final long nowUptime = SystemClock.uptimeMillis();
- if (constantTimeLimit > (nowUptime - lastTopTime)) {
+ if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) {
+ // Discard any other messages for this service
+ mFGSAnrTimer.discard(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
// The app was in the TOP state after the FGS was started so its time allowance
// should be counted from that time since this is considered a user interaction
- mFGSAnrTimer.discard(sr);
final Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
mAm.mHandler.sendMessageAtTime(msg, lastTopTime + constantTimeLimit);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c47e42dfd07e..1b59c1829d4c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5034,7 +5034,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public final void finishAttachApplication(long startSeq) {
+ public final void finishAttachApplication(long startSeq, long timestampApplicationOnCreateNs) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -5054,6 +5054,11 @@ public class ActivityManagerService extends IActivityManager.Stub
} finally {
Binder.restoreCallingIdentity(origId);
}
+
+ if (android.app.Flags.appStartInfoTimestamps() && timestampApplicationOnCreateNs > 0) {
+ addStartInfoTimestampInternal(ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE,
+ timestampApplicationOnCreateNs, UserHandle.getUserId(uid), uid);
+ }
}
private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) {
@@ -10253,10 +10258,15 @@ public class ActivityManagerService extends IActivityManager.Stub
mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
ALLOW_NON_FULL, "addStartInfoTimestamp", null);
- final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+ addStartInfoTimestampInternal(key, timestampNs, userId, callingUid);
+ }
- mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
- UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
+ private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) {
+ mProcessList.getAppStartInfoTracker().addTimestampToStart(
+ Settings.getPackageNameForUid(mContext, uid),
+ UserHandle.getUid(userId, UserHandle.getAppId(uid)),
+ timestampNs,
+ key);
}
@Override
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8b64538ac33c..9b83ede09da4 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -35,6 +35,10 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
/**
* Maps system settings to system properties.
@@ -320,15 +324,30 @@ public class SettingsToPropertiesMapper {
NAMESPACE_REBOOT_STAGING,
AsyncTask.THREAD_POOL_EXECUTOR,
(DeviceConfig.Properties properties) -> {
- String scope = properties.getNamespace();
- for (String key : properties.getKeyset()) {
- String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
- if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/" + key);
- return;
+
+ HashMap<String, HashMap<String, String>> propsToStage =
+ getStagedFlagsWithValueChange(properties);
+
+ for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagValuesToStage = entry.getValue();
+
+ for (String flagName : flagValuesToStage.keySet()) {
+ String stagedValue = flagValuesToStage.get(flagName);
+ String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+ actualNamespace, flagName);
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ log("unable to construct system property for " + actualNamespace
+ + "/" + flagName);
+ continue;
+ }
+
+ setProperty(propertyName, stagedValue);
}
- setProperty(aconfigPropertyName, properties.getString(key, null));
}
+
});
}
@@ -401,25 +420,18 @@ public class SettingsToPropertiesMapper {
}
/**
- * system property name constructing rule for staged aconfig flags, the flag name
- * is in the form of [namespace]*[actual flag name], we should push the following
- * to system properties
- * "next_boot.[actual sys prop name]".
+ * system property name constructing rule for aconfig flags:
+ * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
* If the name contains invalid characters or substrings for system property name,
* will return null.
+ * @param categoryName
* @param flagName
* @return
*/
@VisibleForTesting
- static String makeAconfigFlagStagedPropertyName(String flagName) {
- int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
- if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- log("invalid staged flag: " + flagName);
- return null;
- }
-
- String propertyName = "next_boot." + makeAconfigFlagPropertyName(
- flagName.substring(0, idx), flagName.substring(idx+1));
+ static String makeAconfigFlagPropertyName(String categoryName, String flagName) {
+ String propertyName = SYSTEM_PROPERTY_PREFIX + "aconfig_flags." +
+ categoryName + "." + flagName;
if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
|| propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
@@ -430,25 +442,60 @@ public class SettingsToPropertiesMapper {
}
/**
- * system property name constructing rule for aconfig flags:
- * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
- * If the name contains invalid characters or substrings for system property name,
- * will return null.
- * @param categoryName
- * @param flagName
- * @return
+ * Get the flags that need to be staged in sys prop, only these with a real value
+ * change needs to be staged in sys prop. Otherwise, the flag stage is useless and
+ * create performance problem at sys prop side.
+ * @param properties
+ * @return a hash map of namespace name to actual flags to stage
*/
@VisibleForTesting
- static String makeAconfigFlagPropertyName(String categoryName, String flagName) {
- String propertyName = SYSTEM_PROPERTY_PREFIX + "aconfig_flags." +
- categoryName + "." + flagName;
+ static HashMap<String, HashMap<String, String>> getStagedFlagsWithValueChange(
+ DeviceConfig.Properties properties) {
- if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
- || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
- return null;
+ // sort flags by actual namespace of the flag
+ HashMap<String, HashMap<String, String>> stagedProps = new HashMap<>();
+ for (String flagName : properties.getKeyset()) {
+ int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ log("invalid staged flag: " + flagName);
+ continue;
+ }
+ String actualNamespace = flagName.substring(0, idx);
+ String actualFlagName = flagName.substring(idx+1);
+ HashMap<String, String> flagStagedValues = stagedProps.get(actualNamespace);
+ if (flagStagedValues == null) {
+ flagStagedValues = new HashMap<String, String>();
+ stagedProps.put(actualNamespace, flagStagedValues);
+ }
+ flagStagedValues.put(actualFlagName, properties.getString(flagName, null));
+ }
+
+ // for each namespace, find flags with real flag value change
+ HashMap<String, HashMap<String, String>> propsToStage = new HashMap<>();
+ for (HashMap.Entry<String, HashMap<String, String>> entry : stagedProps.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagStagedValues = entry.getValue();
+ Map<String, String> flagCurrentValues = Settings.Config.getStrings(
+ actualNamespace, new ArrayList<String>(flagStagedValues.keySet()));
+
+ HashMap<String, String> flagsToStage = new HashMap<>();
+ for (String flagName : flagStagedValues.keySet()) {
+ String stagedValue = flagStagedValues.get(flagName);
+ String currentValue = flagCurrentValues.get(flagName);
+ if (currentValue == null) {
+ currentValue = new String("false");
+ }
+ if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) {
+ flagsToStage.put(flagName, stagedValue);
+ }
}
- return propertyName;
+ if (!flagsToStage.isEmpty()) {
+ propsToStage.put(actualNamespace, flagsToStage);
+ }
+ }
+
+ return propsToStage;
}
private void setProperty(String key, String value) {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 94baf88e4853..2285826c0b58 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,6 +24,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -134,7 +135,7 @@ final class AttributedOp {
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -855,7 +856,7 @@ final class AttributedOp {
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
if (recycled != null) {
@@ -881,10 +882,11 @@ final class AttributedOp {
AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
@Nullable String packageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag,
+ @Nullable String deviceId) {
AppOpsManager.OpEventProxyInfo recycled = acquire();
if (recycled != null) {
- recycled.reinit(uid, packageName, attributionTag);
+ recycled.reinit(uid, packageName, attributionTag, deviceId);
return recycled;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 77654d4a5413..da528a2591a1 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1772,6 +1772,7 @@ public class AudioDeviceBroker {
@Override
public void handleMessage(Message msg) {
+ int muteCheckDelayMs = BTA2DP_MUTE_CHECK_DELAY_MS;
switch (msg.what) {
case MSG_RESTORE_DEVICES:
synchronized (mSetModeLock) {
@@ -1870,7 +1871,7 @@ public class AudioDeviceBroker {
btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
"MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+ muteCheckDelayMs += mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
codecAndChanged.first, codecAndChanged.second,
BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
@@ -2060,7 +2061,7 @@ public class AudioDeviceBroker {
// Give some time to Bluetooth service to post a connection message
// in case of active device switch
if (MESSAGES_MUTE_MUSIC.contains(msg.what)) {
- sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS);
+ sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, muteCheckDelayMs);
}
if (isMessageHandledUnderWakelock(msg.what)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9bdc51efb76f..c9612caf67a0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -864,9 +864,25 @@ public class AudioDeviceInventory {
}
}
+ // Additional delay added to the music mute duration when a codec config change is executed.
+ static final int BT_CONFIG_CHANGE_MUTE_DELAY_MS = 500;
+ /**
+ * Handles a Bluetooth link codec configuration change communicated by the Bluetooth stack.
+ * Called when either A2DP or LE Audio codec encoding or sampling rate changes:
+ * the change is communicated to native audio policy to eventually reconfigure the audio
+ * path.
+ * Also used to notify a change in preferred mode (duplex or output) for Bluetooth profiles.
+ *
+ * @param btInfo contains all information on the Bluetooth device and profile
+ * @param codec the requested audio encoding (e.g SBC)
+ * @param codecChanged true if a codec parameter changed, false for preferred mode change
+ * @param event currently only EVENT_DEVICE_CONFIG_CHANGE
+ * @return an optional additional delay in milliseconds to add to the music mute period in
+ * case of an actual codec reconfiguration.
+ */
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- /*package*/ void onBluetoothDeviceConfigChange(
+ /*package*/ int onBluetoothDeviceConfigChange(
@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
@AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
boolean codecChanged, int event) {
@@ -874,10 +890,11 @@ public class AudioDeviceInventory {
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
+ int delayMs = 0;
final BluetoothDevice btDevice = btInfo.mDevice;
if (btDevice == null) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
- return;
+ return delayMs;
}
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
@@ -899,7 +916,7 @@ public class AudioDeviceInventory {
.printSlog(EventLogger.Event.ALOGI, TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
.record();
- return;
+ return delayMs;
}
final String key = DeviceInfo.makeDeviceListKey(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -907,7 +924,7 @@ public class AudioDeviceInventory {
if (di == null) {
Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
- return;
+ return delayMs;
}
mmi.set(MediaMetrics.Property.ADDRESS, address)
@@ -915,7 +932,6 @@ public class AudioDeviceInventory {
.set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName);
-
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
if (btInfo.mProfile == BluetoothProfile.A2DP
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO
@@ -943,6 +959,7 @@ public class AudioDeviceInventory {
+ address
+ " codec=" + AudioSystem.audioFormatToString(codec))
.printSlog(EventLogger.Event.ALOGI, TAG));
+ delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS;
}
}
}
@@ -952,6 +969,7 @@ public class AudioDeviceInventory {
}
}
mmi.record();
+ return delayMs;
}
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index d669c8d4656f..030ce12f5063 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -61,6 +61,8 @@ class AudioManagerShellCommand extends ShellCommand {
return getSoundDoseValue();
case "reset-sound-dose-timeout":
return resetSoundDoseTimeout();
+ case "set-ringer-mode":
+ return setRingerMode();
case "set-volume":
return setVolume();
case "set-device-volume":
@@ -100,6 +102,8 @@ class AudioManagerShellCommand extends ShellCommand {
pw.println(" Returns the current sound dose value");
pw.println(" reset-sound-dose-timeout");
pw.println(" Resets the sound dose timeout used for momentary exposure");
+ pw.println(" set-ringer-mode NORMAL|SILENT|VIBRATE");
+ pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
@@ -150,6 +154,34 @@ class AudioManagerShellCommand extends ShellCommand {
return 0;
}
+ private int setRingerMode() {
+ String ringerModeText = getNextArg();
+ if (ringerModeText == null) {
+ getErrPrintWriter().println("Error: no ringer mode specified");
+ return 1;
+ }
+
+ final int ringerMode = getRingerMode(ringerModeText);
+ if (!AudioManager.isValidRingerMode(ringerMode)) {
+ getErrPrintWriter().println(
+ "Error: invalid value of ringerMode, should be one of NORMAL|SILENT|VIBRATE");
+ return 1;
+ }
+
+ final AudioManager am = mService.mContext.getSystemService(AudioManager.class);
+ am.setRingerModeInternal(ringerMode);
+ return 0;
+ }
+
+ private int getRingerMode(String ringerModeText) {
+ return switch (ringerModeText) {
+ case "NORMAL" -> AudioManager.RINGER_MODE_NORMAL;
+ case "VIBRATE" -> AudioManager.RINGER_MODE_VIBRATE;
+ case "SILENT" -> AudioManager.RINGER_MODE_SILENT;
+ default -> -1;
+ };
+ }
+
private int getIsSurroundFormatEnabled() {
String surroundFormatText = getNextArg();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 559462a50f4f..b0e7575689ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -218,6 +218,7 @@ public class Sensor {
}
@VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+ Slog.d(TAG, "getSessionForUser: mCurrentSession: " + mCurrentSession);
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index b7e3f70a5763..1c6dfe0f5b24 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -242,6 +242,7 @@ public class Sensor {
}
@Nullable protected AidlSession getSessionForUser(int userId) {
+ Slog.d(TAG, "getSessionForUser: mCurrentSession: " + mCurrentSession);
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 68e2bd685fac..7106e894ac31 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2764,21 +2764,9 @@ public final class DisplayManagerService extends SystemService {
display.setHasContentLocked(hasContent);
shouldScheduleTraversal = true;
}
- if (requestedModeId == 0 && requestedRefreshRate != 0) {
- // Scan supported modes returned by display.getInfo() to find a mode with the same
- // size as the default display mode but with the specified refresh rate instead.
- Display.Mode mode = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
- requestedRefreshRate);
- if (mode != null) {
- requestedModeId = mode.getModeId();
- } else {
- Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
- + requestedRefreshRate + " on Display: " + displayId);
- }
- }
- mDisplayModeDirector.getAppRequestObserver().setAppRequest(
- displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate);
+ mDisplayModeDirector.getAppRequestObserver().setAppRequest(displayId, requestedModeId,
+ requestedRefreshRate, requestedMinRefreshRate, requestedMaxRefreshRate);
// TODO(b/202378408) set minimal post-processing only if it's supported once we have a
// separate API for disabling on-device processing.
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index d3de71ea1299..cd07f5a399ed 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -159,6 +159,11 @@ public class DisplayManagerFlags {
Flags::enablePeakRefreshRatePhysicalLimit
);
+ private final FlagState mIgnoreAppPreferredRefreshRate = new FlagState(
+ Flags.FLAG_IGNORE_APP_PREFERRED_REFRESH_RATE_REQUEST,
+ Flags::ignoreAppPreferredRefreshRateRequest
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -322,6 +327,13 @@ public class DisplayManagerFlags {
}
/**
+ * @return Whether to ignore preferredRefreshRate app request or not
+ */
+ public boolean ignoreAppPreferredRefreshRateRequest() {
+ return mIgnoreAppPreferredRefreshRate.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3d64fc2b160c..a15a8e8e85d1 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -255,3 +255,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ignore_app_preferred_refresh_rate_request"
+ namespace: "display_manager"
+ description: "Feature flag for DisplayManager to ignore preferred refresh rate app request. It will be handled by SF only."
+ bug: "330810426"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 3084daeaa382..91bd80eb9037 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -222,7 +222,7 @@ public class DisplayModeDirector {
displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
- mAppRequestObserver = new AppRequestObserver();
+ mAppRequestObserver = new AppRequestObserver(displayManagerFlags);
mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler, displayManagerFlags);
@@ -1205,17 +1205,32 @@ public class DisplayModeDirector {
public final class AppRequestObserver {
private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
+ private final boolean mIgnorePreferredRefreshRate;
- AppRequestObserver() {
+ AppRequestObserver(DisplayManagerFlags flags) {
mAppRequestedModeByDisplay = new SparseArray<>();
mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
+ mIgnorePreferredRefreshRate = flags.ignoreAppPreferredRefreshRateRequest();
}
/**
* Sets refresh rates from app request
*/
- public void setAppRequest(int displayId, int modeId,
+ public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
+
+ if (modeId == 0 && requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) {
+ // Scan supported modes returned to find a mode with the same
+ // size as the default display mode but with the specified refresh rate instead.
+ Display.Mode mode = findDefaultModeByRefreshRate(displayId, requestedRefreshRate);
+ if (mode != null) {
+ modeId = mode.getModeId();
+ } else {
+ Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
+ + requestedRefreshRate + " on Display: " + displayId);
+ }
+ }
+
synchronized (mLock) {
setAppRequestedModeLocked(displayId, modeId);
setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
@@ -1223,6 +1238,23 @@ public class DisplayModeDirector {
}
}
+ @Nullable
+ private Display.Mode findDefaultModeByRefreshRate(int displayId, float refreshRate) {
+ Display.Mode[] modes;
+ Display.Mode defaultMode;
+ synchronized (mLock) {
+ modes = mSupportedModesByDisplay.get(displayId);
+ defaultMode = mDefaultModeByDisplay.get(displayId);
+ }
+ for (int i = 0; i < modes.length; i++) {
+ if (modes[i].matches(defaultMode.getPhysicalWidth(),
+ defaultMode.getPhysicalHeight(), refreshRate)) {
+ return modes[i];
+ }
+ }
+ return null;
+ }
+
private void setAppRequestedModeLocked(int displayId, int modeId) {
final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
diff --git a/services/core/java/com/android/server/dreams/Android.bp b/services/core/java/com/android/server/dreams/Android.bp
new file mode 100644
index 000000000000..4078a42a09ec
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+ name: "dreams_flags",
+ package: "com.android.server.dreams",
+ container: "system",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "dreams_flags_lib",
+ aconfig_declarations: "dreams_flags",
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fc63494d3c99..2def5aed2478 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -45,6 +45,7 @@ import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
import android.net.Uri;
import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -118,6 +119,7 @@ public final class DreamManagerService extends SystemService {
private final DreamController mController;
private final PowerManager mPowerManager;
private final PowerManagerInternal mPowerManagerInternal;
+ private final BatteryManagerInternal mBatteryManagerInternal;
private final PowerManager.WakeLock mDozeWakeLock;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPmInternal;
@@ -186,7 +188,11 @@ public final class DreamManagerService extends SystemService {
private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ if (Flags.useBatteryChangedBroadcast()) {
+ mIsCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ } else {
+ mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ }
}
};
@@ -251,6 +257,12 @@ public final class DreamManagerService extends SystemService {
com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
+
+ if (Flags.useBatteryChangedBroadcast()) {
+ mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+ } else {
+ mBatteryManagerInternal = null;
+ }
}
@Override
@@ -279,9 +291,15 @@ public final class DreamManagerService extends SystemService {
mContext.registerReceiver(
mDockStateReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+
IntentFilter chargingIntentFilter = new IntentFilter();
- chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
- chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ if (Flags.useBatteryChangedBroadcast()) {
+ chargingIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ chargingIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ } else {
+ chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
+ chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ }
mContext.registerReceiver(mChargingReceiver, chargingIntentFilter);
mSettingsObserver = new SettingsObserver(mHandler);
diff --git a/services/core/java/com/android/server/dreams/flags.aconfig b/services/core/java/com/android/server/dreams/flags.aconfig
new file mode 100644
index 000000000000..5d35ebd629cd
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.dreams"
+container: "system"
+
+flag {
+ name: "use_battery_changed_broadcast"
+ namespace: "communal"
+ description: "Use ACTION_BATTERY_CHANGED broadcast to track charging state"
+ bug: "329125239"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 138186ba6191..4c5a3c27e156 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -333,8 +333,8 @@ public class InputShellCommand extends ShellCommand {
out.println();
out.println("The commands and default sources are:");
out.println(" text <string> (Default: keyboard)");
- out.println(" keyevent [--longpress|--doubletap|--async"
- + "|--delay <duration between keycodes in ms>]"
+ out.println(" keyevent [--longpress|--duration <duration to hold key down in ms>]"
+ + " [--doubletap] [--async] [--delay <duration between keycodes in ms>]"
+ " <key code number or name> ..."
+ " (Default: keyboard)");
out.println(" tap <x> <y> (Default: touchscreen)");
@@ -402,6 +402,7 @@ public class InputShellCommand extends ShellCommand {
boolean async = false;
boolean doubleTap = false;
long delayMs = 0;
+ long durationMs = 0;
String arg = getNextArgRequired();
do {
@@ -411,9 +412,21 @@ public class InputShellCommand extends ShellCommand {
doubleTap = (doubleTap || arg.equals("--doubletap"));
if (arg.equals("--delay")) {
delayMs = Long.parseLong(getNextArgRequired());
+ } else if (arg.equals("--duration")) {
+ durationMs = Long.parseLong(getNextArgRequired());
}
} while ((arg = getNextArg()) != null);
+ if (durationMs > 0 && longPress) {
+ getErrPrintWriter().println(
+ "--duration and --longpress cannot be used at the same time.");
+ throw new IllegalArgumentException(
+ "keyevent args should only contain either durationMs or longPress");
+ }
+ if (longPress) {
+ durationMs = ViewConfiguration.getLongPressTimeout();
+ }
+
boolean firstInput = true;
do {
if (!firstInput && delayMs > 0) {
@@ -422,16 +435,17 @@ public class InputShellCommand extends ShellCommand {
firstInput = false;
final int keyCode = KeyEvent.keyCodeFromString(arg);
- sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+ sendKeyEvent(inputSource, keyCode, durationMs, displayId, async);
if (doubleTap) {
sleep(ViewConfiguration.getDoubleTapMinTime());
- sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+ sendKeyEvent(inputSource, keyCode, durationMs, displayId, async);
}
} while ((arg = getNextArg()) != null);
}
private void sendKeyEvent(
- int inputSource, int keyCode, boolean longPress, int displayId, boolean async) {
+ int inputSource, int keyCode, long durationMs, int displayId,
+ boolean async) {
final long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */,
@@ -440,13 +454,23 @@ public class InputShellCommand extends ShellCommand {
event.setDisplayId(displayId);
injectKeyEvent(event, async);
- if (longPress) {
- sleep(ViewConfiguration.getLongPressTimeout());
- // Some long press behavior would check the event time, we set a new event time here.
- final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
- KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(
- event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
- injectKeyEvent(longPressEvent, async);
+ long firstSleepDurationMs = Math.min(durationMs, ViewConfiguration.getLongPressTimeout());
+ if (firstSleepDurationMs > 0) {
+ sleep(firstSleepDurationMs);
+ // Send FLAG_LONG_PRESS right after `longPressTimeout`, and resume sleep if needed.
+ if (durationMs >= ViewConfiguration.getLongPressTimeout()) {
+ // Some long press behavior would check the event time, we set a new event time
+ // here.
+ final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
+ KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(event, nextEventTime,
+ 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
+ injectKeyEvent(longPressEvent, async);
+
+ long secondSleepDurationMs = durationMs - firstSleepDurationMs;
+ if (secondSleepDurationMs > 0) {
+ sleep(secondSleepDurationMs);
+ }
+ }
}
injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP), async);
}
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 4fc1a17032e9..ad6b0ca71527 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit-large": [
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 96f32f30877c..bf49671e2d82 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static android.app.Flags.updateRankingTime;
+import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -497,7 +497,7 @@ public final class NotificationAttentionHelper {
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted");
}
- if (updateRankingTime()) {
+ if (sortSectionByTime()) {
if (buzz || beep) {
record.resetRankingTime();
}
@@ -1528,7 +1528,7 @@ public final class NotificationAttentionHelper {
// recent conversation
if (record.isConversation()
- && record.getNotification().when > mLastAvalancheTriggerTimestamp) {
+ && record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9d4ab11d7001..ca6ae63e74fc 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
@@ -8593,7 +8594,7 @@ public class NotificationManagerService extends SystemService {
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
- if (android.app.Flags.updateRankingTime()) {
+ if (sortSectionByTime()) {
if (isInterruptive) {
r.resetRankingTime();
}
@@ -8738,7 +8739,7 @@ public class NotificationManagerService extends SystemService {
return false;
}
- if (android.app.Flags.updateRankingTime()) {
+ if (sortSectionByTime()) {
// Ignore visual interruptions from FGS/UIJs because users
// consider them one 'session'. Count them for everything else.
if (r.getSbn().getNotification().isFgsOrUij()) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 38c95f771601..0c6a6c84b69b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -18,7 +18,7 @@ package com.android.server.notification;
import static android.app.Flags.restrictAudioAttributesAlarm;
import static android.app.Flags.restrictAudioAttributesCall;
import static android.app.Flags.restrictAudioAttributesMedia;
-import static android.app.Flags.updateRankingTime;
+import static android.app.Flags.sortSectionByTime;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -580,7 +580,7 @@ public final class NotificationRecord {
pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
pw.println(prefix + "number=" + notification.number);
pw.println(prefix + "groupAlertBehavior=" + notification.getGroupAlertBehavior());
- pw.println(prefix + "when=" + notification.when);
+ pw.println(prefix + "when=" + notification.when + "/" + notification.getWhen());
pw.print(prefix + "tickerText=");
if (!TextUtils.isEmpty(notification.tickerText)) {
@@ -1092,9 +1092,9 @@ public final class NotificationRecord {
private long calculateRankingTimeMs(long previousRankingTimeMs) {
Notification n = getNotification();
// Take developer provided 'when', unless it's in the future.
- if (updateRankingTime()) {
- if (n.hasAppProvidedWhen() && n.when <= getSbn().getPostTime()){
- return n.when;
+ if (sortSectionByTime()) {
+ if (n.hasAppProvidedWhen() && n.getWhen() <= getSbn().getPostTime()){
+ return n.getWhen();
}
} else {
if (n.when != 0 && n.when <= getSbn().getPostTime()) {
@@ -1211,7 +1211,7 @@ public final class NotificationRecord {
}
public void resetRankingTime() {
- if (updateRankingTime()) {
+ if (sortSectionByTime()) {
mRankingTimeMs = calculateRankingTimeMs(getSbn().getPostTime());
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 9a6ea2c2aeb8..65ef53f1df14 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -542,7 +542,7 @@ interface NotificationRecordLogger {
this.is_locked = p.r.isLocked();
this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes(
- p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().when);
+ p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().getWhen());
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1f2ad07ea684..309e9450a01d 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -26,6 +26,7 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.os.UserHandle.USER_SYSTEM;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -139,6 +140,8 @@ public class PreferencesHelper implements RankingConfig {
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
+
+ private static final String ATT_USERID = "userid";
private static final String ATT_ID = "id";
private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
private static final String ATT_PRIORITY = "priority";
@@ -268,7 +271,7 @@ public class PreferencesHelper implements RankingConfig {
}
if (type == XmlPullParser.START_TAG) {
if (TAG_STATUS_ICONS.equals(tag)) {
- if (forRestore && userId != UserHandle.USER_SYSTEM) {
+ if (forRestore && userId != USER_SYSTEM) {
continue;
}
mHideSilentStatusBarIcons = parser.getAttributeBoolean(null,
@@ -311,8 +314,16 @@ public class PreferencesHelper implements RankingConfig {
: parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ // when data is loaded from disk it's loaded as USER_ALL, but restored data that
+ // is pending app install needs the user id that the data was restored to
+ int fixedUserId = userId;
+ if (Flags.persistIncompleteRestoreData()) {
+ if (!forRestore && uid == UNKNOWN_UID) {
+ fixedUserId = parser.getAttributeInt(null, ATT_USERID, USER_SYSTEM);
+ }
+ }
PackagePreferences r = getOrCreatePackagePreferencesLocked(
- name, userId, uid,
+ name, fixedUserId, uid,
appImportance,
parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
@@ -504,6 +515,9 @@ public class PreferencesHelper implements RankingConfig {
}
if (r.uid == UNKNOWN_UID) {
+ if (Flags.persistIncompleteRestoreData()) {
+ r.userId = userId;
+ }
mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
} else {
mPackagePreferences.put(key, r);
@@ -674,6 +688,7 @@ public class PreferencesHelper implements RankingConfig {
if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
+ out.attributeInt(null, ATT_USERID, r.userId);
}
if (!forBackup) {
@@ -2947,6 +2962,8 @@ public class PreferencesHelper implements RankingConfig {
boolean migrateToPm = false;
long creationTime;
+ @UserIdInt int userId;
+
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 77568015fe79..03dd9351efc7 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -147,11 +147,9 @@ public class RankingHelper {
if (sortSectionByTime()) {
final String groupKey = record.getGroupKey();
NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
- // summaries are mostly hidden in systemui - if there is a child notification
- // with better information, use its rank
- if (existingProxy == null
- || (existingProxy.getNotification().isGroupSummary()
- && !existingProxy.getNotification().hasAppProvidedWhen())) {
+ // summaries are mostly hidden in systemui - if there is a child notification,
+ // use its rank
+ if (existingProxy == null || existingProxy.getNotification().isGroupSummary()) {
mProxyByGroupTmp.put(groupKey, record);
}
} else {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 474768935bbd..143bc5cb20ff 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1725,7 +1725,28 @@ public class ZenModeHelper {
synchronized (mConfigLock) {
if (policy == null || mConfig == null) return;
final ZenModeConfig newConfig = mConfig.copy();
- newConfig.applyNotificationPolicy(policy);
+ if (Flags.modesApi() && !Flags.modesUi()) {
+ // Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
+ // the user cannot edit zen policy to emulate the previous "inheritance".
+ ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ newConfig.toNotificationPolicy());
+ ZenPolicy newPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+
+ newConfig.applyNotificationPolicy(policy);
+
+ if (!previousPolicy.equals(newPolicy)) {
+ for (ZenRule rule : newConfig.automaticRules.values()) {
+ if (!SystemZenRules.isSystemOwnedRule(rule)
+ && rule.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ && (rule.zenPolicy == null || rule.zenPolicy.equals(previousPolicy)
+ || rule.zenPolicy.equals(getDefaultZenPolicy()))) {
+ rule.zenPolicy = newPolicy;
+ }
+ }
+ }
+ } else {
+ newConfig.applyNotificationPolicy(policy);
+ }
setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index c8fd7e47d80a..8a853287738b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -19,6 +19,7 @@ package com.android.server.os;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
@@ -68,6 +69,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
@@ -335,14 +337,22 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
static class Injector {
+ class RoleManagerWrapper {
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return mContext.getSystemService(RoleManager.class).getRoleHolders(roleName);
+ }
+ }
+
Context mContext;
ArraySet<String> mAllowlistedPackages;
AtomicFile mMappingFile;
+ RoleManagerWrapper mRoleManagerWrapper;
Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile) {
mContext = context;
mAllowlistedPackages = allowlistedPackages;
mMappingFile = mappingFile;
+ mRoleManagerWrapper = new RoleManagerWrapper();
}
Context getContext() {
@@ -368,6 +378,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
void setSystemProperty(String key, String value) {
SystemProperties.set(key, value);
}
+
+ RoleManagerWrapper getRoleManagerWrapper() {
+ return mRoleManagerWrapper;
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -546,7 +560,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
if (!allowlisted) {
final long token = Binder.clearCallingIdentity();
try {
- allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+ allowlisted = mInjector.getRoleManagerWrapper().getRoleHolders(
ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0f4e4821dee8..ae485ede1bec 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3984,6 +3984,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
// packageName -> list of components to send broadcasts now
final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize);
+ final List<PackageMetrics.ComponentStateMetrics> componentStateMetricsList =
+ new ArrayList<PackageMetrics.ComponentStateMetrics>();
synchronized (mLock) {
Computer computer = snapshotComputer();
boolean scheduleBroadcastMessage = false;
@@ -3997,11 +3999,17 @@ public class PackageManagerService implements PackageSender, TestUtilityService
// update enabled settings
final ComponentEnabledSetting setting = settings.get(i);
final String packageName = setting.getPackageName();
- if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName),
- setting, userId, callingPackage)) {
+ final PackageSetting packageSetting = pkgSettings.get(packageName);
+ final PackageMetrics.ComponentStateMetrics componentStateMetrics =
+ new PackageMetrics.ComponentStateMetrics(setting,
+ UserHandle.getUid(userId, packageSetting.getAppId()),
+ packageSetting.getEnabled(userId));
+ if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
+ callingPackage)) {
continue;
}
anyChanged = true;
+ componentStateMetricsList.add(componentStateMetrics);
if ((setting.getEnabledFlags() & PackageManager.SYNCHRONOUS) != 0) {
isSynchronous = true;
@@ -4029,6 +4037,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return;
}
+ // Log the metrics when the component state is changed.
+ PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
+
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index a0b6897a080e..20598f91a51d 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,21 @@ package com.android.server.pm;
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.SecurityLog;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
@@ -41,12 +50,14 @@ import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Metrics class for reporting stats to logging infrastructures like statsd
*/
final class PackageMetrics {
+ private static final String TAG = "PackageMetrics";
public static final int STEP_PREPARE = 1;
public static final int STEP_SCAN = 2;
public static final int STEP_RECONCILE = 3;
@@ -344,4 +355,76 @@ final class PackageMetrics {
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
userId);
}
+
+ public static class ComponentStateMetrics {
+ public int mUid;
+ public int mComponentOldState;
+ public int mComponentNewState;
+ public boolean mIsForWholeApp;
+ @NonNull private String mPackageName;
+ @Nullable private String mClassName;
+
+ ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
+ int componentOldState) {
+ mUid = uid;
+ mComponentOldState = componentOldState;
+ mComponentNewState = setting.getEnabledState();
+ mIsForWholeApp = !setting.isComponent();
+ mPackageName = setting.getPackageName();
+ mClassName = setting.getClassName();
+ }
+
+ public boolean isSameComponent(ActivityInfo activityInfo) {
+ if (activityInfo == null) {
+ return false;
+ }
+ return mIsForWholeApp ? TextUtils.equals(activityInfo.packageName, mPackageName)
+ : activityInfo.getComponentName().equals(
+ new ComponentName(mPackageName, mClassName));
+ }
+ }
+
+ public static void reportComponentStateChanged(@NonNull Computer computer,
+ List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId) {
+ if (!Flags.componentStateChangedMetrics()) {
+ return;
+ }
+ if (componentStateMetricsList == null || componentStateMetricsList.isEmpty()) {
+ Slog.d(TAG, "Fail to report component state due to metrics is empty");
+ return;
+ }
+ boolean isLauncher = false;
+ final List<ResolveInfo> resolveInfosForLauncher = getHomeActivitiesResolveInfoAsUser(
+ computer, userId);
+ final int resolveInfosForLauncherSize =
+ resolveInfosForLauncher != null ? resolveInfosForLauncher.size() : 0;
+ final int metricsSize = componentStateMetricsList.size();
+ for (int i = 0; i < metricsSize; i++) {
+ final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i);
+ for (int j = 0; j < resolveInfosForLauncherSize; j++) {
+ ResolveInfo resolveInfo = resolveInfosForLauncher.get(j);
+ if (componentStateMetrics.isSameComponent(resolveInfo.activityInfo)) {
+ isLauncher = true;
+ break;
+ }
+ }
+ reportComponentStateChanged(componentStateMetrics.mUid,
+ componentStateMetrics.mComponentOldState,
+ componentStateMetrics.mComponentNewState,
+ isLauncher,
+ componentStateMetrics.mIsForWholeApp);
+ }
+ }
+
+ private static void reportComponentStateChanged(int uid, int componentOldState,
+ int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+ FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
+ uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+ }
+
+ private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
+ @UserIdInt int userId) {
+ return computer.queryIntentActivitiesInternal(computer.getHomeIntent(), /* resolvedType */
+ null, /* flags */ 0, userId);
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 5e2467304b1f..00582bfa90c5 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -46,7 +46,7 @@ import java.util.List;
/**
* Launcher information used by {@link ShortcutService}.
*
- * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ * All methods should be guarded by {@code ShortcutPackageItem#mPackageItemLock}.
*/
class ShortcutLauncher extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -66,7 +66,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
/**
* Package name -> IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
@@ -99,7 +99,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
*/
private void onRestoreBlocked() {
final ArrayList<UserPackage> pinnedPackages;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedPackages = new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
}
@@ -138,7 +138,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
final int idSize = ids.size();
if (idSize == 0) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mPinnedShortcuts.remove(up);
}
} else {
@@ -165,7 +165,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
floatingSet.add(id);
}
}
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> prevSet = mPinnedShortcuts.get(up);
if (prevSet != null) {
for (String id : floatingSet) {
@@ -187,7 +187,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
@Nullable
public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
@UserIdInt int packageUserId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinnedShortcuts = mPinnedShortcuts.get(
UserPackage.of(packageUserId, packageName));
return pinnedShortcuts == null ? null : new ArraySet<>(pinnedShortcuts);
@@ -198,7 +198,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
* Return true if the given shortcut is pinned by this launcher.<code></code>
*/
public boolean hasPinned(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinned = mPinnedShortcuts.get(
UserPackage.of(shortcut.getUserId(), shortcut.getPackage()));
return (pinned != null) && pinned.contains(shortcut.getId());
@@ -211,7 +211,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
String id, boolean forPinRequest) {
final ArrayList<String> pinnedList;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinnedSet = mPinnedShortcuts.get(
UserPackage.of(packageUserId, packageName));
if (pinnedSet != null) {
@@ -227,7 +227,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null;
}
}
@@ -253,7 +253,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
return;
}
final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts);
}
final int size = pinnedShortcuts.size();
@@ -366,7 +366,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
: ShortcutService.parseIntAttribute(parser,
ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
- synchronized (ret.mLock) {
+ synchronized (ret.mPackageItemLock) {
ret.mPinnedShortcuts.put(
UserPackage.of(packageUserId, packageName), ids);
}
@@ -407,7 +407,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
pw.println();
final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts);
}
final int size = pinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 449e9ab8c5ec..c929c1f2fcfc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -163,20 +163,20 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on
* IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
/**
* All the share targets from the package
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
/**
@@ -193,10 +193,10 @@ class ShortcutPackage extends ShortcutPackageItem {
private long mLastKnownForegroundElapsedTime;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private long mLastReportedTime;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -233,7 +233,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
public int getShortcutCount() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcuts.size();
}
}
@@ -276,7 +276,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@Nullable
public ShortcutInfo findShortcutById(@Nullable final String id) {
if (id == null) return null;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcuts.get(id);
}
}
@@ -354,7 +354,7 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
shortcut = mShortcuts.remove(id);
if (shortcut != null) {
removeIcon(shortcut);
@@ -409,7 +409,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (newShortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)) {
if (isAppSearchEnabled()) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mTransientShortcuts.put(newShortcut.getId(), newShortcut);
}
}
@@ -482,7 +482,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (newShortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)) {
if (isAppSearchEnabled()) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mTransientShortcuts.put(newShortcut.getId(), newShortcut);
}
}
@@ -506,7 +506,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutService service = mShortcutUser.mService;
// Ensure the total number of shortcuts doesn't exceed the hard limit per app.
final int maxShortcutPerApp = service.getMaxAppShortcuts();
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si ->
!si.isPinned()).collect(Collectors.toList());
if (appShortcuts.size() >= maxShortcutPerApp) {
@@ -555,7 +555,7 @@ class ShortcutPackage extends ShortcutPackageItem {
public List<ShortcutInfo> deleteAllDynamicShortcuts() {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic() && si.isVisibleToPublisher()) {
@@ -914,7 +914,7 @@ class ShortcutPackage extends ShortcutPackageItem {
List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
@NonNull final IntentFilter filter, @Nullable final String pkgName) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
for (int i = 0; i < mShareTargets.size(); i++) {
final ShareTargetInfo target = mShareTargets.get(i);
@@ -967,7 +967,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
public boolean hasShareTargets() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return !mShareTargets.isEmpty();
}
}
@@ -978,7 +978,7 @@ class ShortcutPackage extends ShortcutPackageItem {
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (mShareTargets.isEmpty()) {
return 0;
}
@@ -1017,7 +1017,7 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private ArraySet<String> getUsedBitmapFilesLocked() {
final ArraySet<String> usedFiles = new ArraySet<>(1);
forEachShortcut(si -> {
@@ -1029,7 +1029,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
public void cleanupDanglingBitmapFiles(@NonNull File path) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.waitForAllSavesLocked();
final ArraySet<String> usedFiles = getUsedBitmapFilesLocked();
@@ -1136,7 +1136,7 @@ class ShortcutPackage extends ShortcutPackageItem {
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
int shareTargetSize = 0;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
try {
shareTargetSize = mShareTargets.size();
newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
@@ -1680,7 +1680,7 @@ class ShortcutPackage extends ShortcutPackageItem {
void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
@NonNull final String shortcutId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final long currentTS = SystemClock.elapsedRealtime();
final ShortcutService s = mShortcutUser.mService;
if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
@@ -1757,7 +1757,7 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.println(")");
pw.println();
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.dumpLocked(pw, " ");
}
}
@@ -1827,7 +1827,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final int size = mShortcuts.size();
final int shareTargetSize = mShareTargets.size();
@@ -2037,7 +2037,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
- synchronized (ret.mLock) {
+ synchronized (ret.mPackageItemLock) {
ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute(
parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION;
@@ -2283,7 +2283,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@VisibleForTesting
List<ShareTargetInfo> getAllShareTargetsForTest() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return new ArrayList<>(mShareTargets);
}
}
@@ -2404,7 +2404,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@NonNull final Consumer<ShortcutInfo> transform) {
Objects.requireNonNull(id);
Objects.requireNonNull(transform);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (shortcut != null) {
transform.accept(shortcut);
}
@@ -2424,7 +2424,7 @@ class ShortcutPackage extends ShortcutPackageItem {
private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (ShortcutInfo si : shortcuts) {
mShortcuts.put(si.getId(), si);
}
@@ -2433,7 +2433,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@Nullable
List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return ids.stream().map(mShortcuts::get)
.filter(Objects::nonNull).collect(Collectors.toList());
}
@@ -2455,7 +2455,7 @@ class ShortcutPackage extends ShortcutPackageItem {
private void forEachShortcutStopWhen(
@NonNull final Function<ShortcutInfo, Boolean> cb) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (cb.apply(si)) {
@@ -2600,7 +2600,7 @@ class ShortcutPackage extends ShortcutPackageItem {
})));
}
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
@Override
void scheduleSaveToAppSearchLocked() {
final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts);
@@ -2684,7 +2684,7 @@ class ShortcutPackage extends ShortcutPackageItem {
.penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
.build());
future = mShortcutUser.getAppSearch(searchContext);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (!mIsAppSearchSchemaUpToDate) {
future = future.thenCompose(this::setupSchema);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 12115afcbeec..dfd2e08e9ba9 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -39,7 +39,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
- * All methods should be either guarded by {@code #mShortcutUser.mService.mLock} or {@code #mLock}.
+ * All methods should be either guarded by {@code #mPackageItemLock}.
*/
abstract class ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -52,10 +52,10 @@ abstract class ShortcutPackageItem {
protected ShortcutUser mShortcutUser;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
protected final ShortcutBitmapSaver mShortcutBitmapSaver;
- protected final Object mLock = new Object();
+ protected final Object mPackageItemLock = new Object();
protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
int packageUserId, @NonNull String packageName,
@@ -157,7 +157,7 @@ abstract class ShortcutPackageItem {
public abstract void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
public void saveToFileLocked(File path, boolean forBackup) {
try (ResilientAtomicFile file = getResilientFile(path)) {
FileOutputStream os = null;
@@ -187,7 +187,7 @@ abstract class ShortcutPackageItem {
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
void scheduleSaveToAppSearchLocked() {
}
@@ -219,7 +219,7 @@ abstract class ShortcutPackageItem {
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
Slog.d(TAG, "Saving package item " + getPackageName() + " to " + path);
}
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
path.getParentFile().mkdirs();
// TODO: Since we are persisting shortcuts into AppSearch, we should read from/write to
// AppSearch as opposed to maintaining a separate XML file.
@@ -229,14 +229,14 @@ abstract class ShortcutPackageItem {
}
public boolean waitForBitmapSaves() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcutBitmapSaver.waitForAllSavesLocked();
}
}
public void saveBitmap(ShortcutInfo shortcut,
int maxDimension, Bitmap.CompressFormat format, int quality) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality);
}
}
@@ -246,19 +246,19 @@ abstract class ShortcutPackageItem {
*/
@Nullable
public String getBitmapPathMayWait(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut);
}
}
public void removeIcon(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.removeIcon(shortcut);
}
}
void removeShortcutPackageItem() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
getResilientFile(getShortcutPackageItemFile()).delete();
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fe9c3f217841..3f5ec0678f85 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -295,7 +295,7 @@ public class ShortcutService extends IShortcutService.Stub {
final Context mContext;
- private final Object mLock = new Object();
+ private final Object mServiceLock = new Object();
private final Object mNonPersistentUsersLock = new Object();
private final Object mWtfLock = new Object();
@@ -333,7 +333,7 @@ public class ShortcutService extends IShortcutService.Stub {
/**
* User ID -> UserShortcuts
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
/**
@@ -388,13 +388,13 @@ public class ShortcutService extends IShortcutService.Stub {
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
private final ShortcutDumpFiles mShortcutDumpFiles;
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
final SparseIntArray mUidState = new SparseIntArray();
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private List<Integer> mDirtyUserIds = new ArrayList<>();
private final AtomicBoolean mBootCompleted = new AtomicBoolean();
@@ -473,7 +473,7 @@ public class ShortcutService extends IShortcutService.Stub {
@GuardedBy("mWtfLock")
private Exception mLastWtfStacktrace;
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final boolean mIsAppSearchEnabled;
@@ -518,7 +518,7 @@ public class ShortcutService extends IShortcutService.Stub {
mUriPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner(TAG);
mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class));
- mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
+ mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mServiceLock);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false)
@@ -595,7 +595,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Default launcher is removed or changed, revoke all URI permissions.
mUriGrantsManagerInternal.revokeUriPermissionFromOwner(mUriPermissionOwner, null, ~0, 0);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
// Clear the launcher cache for this user. It will be set again next time the default
// launcher is read from RoleManager.
if (isUserLoadedLocked(userId)) {
@@ -622,7 +622,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleOnUidStateChanged");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
mUidState.put(uid, procState);
// We need to keep track of last time an app comes to foreground.
@@ -639,7 +639,7 @@ public class ShortcutService extends IShortcutService.Stub {
return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
boolean isUidForegroundLocked(int uid) {
if (uid == Process.SYSTEM_UID) {
// IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
@@ -655,7 +655,7 @@ public class ShortcutService extends IShortcutService.Stub {
return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid));
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getUidLastForegroundElapsedTimeLocked(int uid) {
return mUidLastForegroundElapsedTime.get(uid);
}
@@ -729,7 +729,7 @@ public class ShortcutService extends IShortcutService.Stub {
final long start = getStatStartTime();
injectRunOnNewThread(() -> {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleUnlockUser");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start);
getUserShortcutsLocked(userId);
}
@@ -743,7 +743,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, "handleStopUser: user=" + userId);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleStopUser");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
unloadUserLocked(userId);
synchronized (mUnlockedUsers) {
@@ -753,7 +753,7 @@ public class ShortcutService extends IShortcutService.Stub {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void unloadUserLocked(int userId) {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "unloadUserLocked: user=" + userId);
@@ -784,7 +784,7 @@ public class ShortcutService extends IShortcutService.Stub {
* Init the instance. (load the state file, etc)
*/
private void initialize() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
loadConfigurationLocked();
loadBaseStateLocked();
}
@@ -1003,7 +1003,7 @@ public class ShortcutService extends IShortcutService.Stub {
FileOutputStream outs = null;
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
outs = file.startWrite();
}
@@ -1029,7 +1029,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void loadBaseStateLocked() {
mRawLastResetTime.set(0);
@@ -1104,7 +1104,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, "Saving to " + file);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
os = file.startWrite();
saveUserInternalLocked(userId, os, /* forBackup= */ false);
}
@@ -1122,7 +1122,7 @@ public class ShortcutService extends IShortcutService.Stub {
getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
boolean forBackup) throws IOException, XmlPullParserException {
@@ -1224,7 +1224,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Scheduling to save for " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!mDirtyUserIds.contains(userId)) {
mDirtyUserIds.add(userId);
}
@@ -1245,7 +1245,7 @@ public class ShortcutService extends IShortcutService.Stub {
try {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutSaveDirtyInfo");
List<Integer> dirtyUserIds = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
List<Integer> tmp = mDirtyUserIds;
mDirtyUserIds = dirtyUserIds;
dirtyUserIds = tmp;
@@ -1266,14 +1266,14 @@ public class ShortcutService extends IShortcutService.Stub {
}
/** Return the last reset time. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getLastResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime.get();
}
/** Return the next reset time. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getNextResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime.get() + mResetInterval;
@@ -1286,7 +1286,7 @@ public class ShortcutService extends IShortcutService.Stub {
/**
* Update the last reset time.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void updateTimesLocked() {
final long now = injectCurrentTimeMillis();
@@ -1315,7 +1315,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- // Requires mLock held, but "Locked" prefix would look weired so we just say "L".
+ // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
protected boolean isUserUnlockedL(@UserIdInt int userId) {
// First, check the local copy.
synchronized (mUnlockedUsers) {
@@ -1331,14 +1331,14 @@ public class ShortcutService extends IShortcutService.Stub {
return mUserManagerInternal.isUserUnlockingOrUnlocked(userId);
}
- // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
+ // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
void throwIfUserLockedL(@UserIdInt int userId) {
if (!isUserUnlockedL(userId)) {
throw new IllegalStateException("User " + userId + " is locked or not running");
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
private boolean isUserLoadedLocked(@UserIdInt int userId) {
return mUsers.get(userId) != null;
@@ -1347,7 +1347,7 @@ public class ShortcutService extends IShortcutService.Stub {
private int mLastLockedUser = -1;
/** Return the per-user state. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
if (!isUserUnlockedL(userId)) {
@@ -1386,7 +1386,7 @@ public class ShortcutService extends IShortcutService.Stub {
return ret;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
for (int i = mUsers.size() - 1; i >= 0; i--) {
c.accept(mUsers.valueAt(i));
@@ -1397,7 +1397,7 @@ public class ShortcutService extends IShortcutService.Stub {
* Return the per-user per-package state. If the caller is a publisher, use
* {@link #getPackageShortcutsForPublisherLocked} instead.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
@@ -1405,7 +1405,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
/** Return the per-user per-package state. Use this when the caller is a publisher. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutPackage getPackageShortcutsForPublisherLocked(
@NonNull String packageName, @UserIdInt int userId) {
@@ -1414,7 +1414,7 @@ public class ShortcutService extends IShortcutService.Stub {
return ret;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutLauncher getLauncherShortcutsLocked(
@NonNull String packageName, @UserIdInt int ownerUserId,
@@ -1443,7 +1443,7 @@ public class ShortcutService extends IShortcutService.Stub {
* {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
* saves are going on.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
@@ -1780,7 +1780,7 @@ public class ShortcutService extends IShortcutService.Stub {
void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) {
Objects.requireNonNull(token);
Objects.requireNonNull(r);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
mHandler.removeCallbacksAndMessages(token);
mHandler.postDelayed(r, token, CALLBACK_DELAY);
}
@@ -2015,7 +2015,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2084,7 +2084,7 @@ public class ShortcutService extends IShortcutService.Stub {
final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2184,7 +2184,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2241,7 +2241,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2306,7 +2306,7 @@ public class ShortcutService extends IShortcutService.Stub {
verifyCaller(packageName, userId);
verifyShortcutInfoPackage(packageName, shortcut);
final Intent intent;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
// Send request to the launcher, if supported.
intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
@@ -2337,7 +2337,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
final boolean ret;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
Preconditions.checkState(isUidForegroundLocked(callingUid),
@@ -2378,7 +2378,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2419,7 +2419,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
List<ShortcutInfo> changedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2449,7 +2449,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2487,7 +2487,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = new ArrayList<>();
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
// Dynamic shortcuts that are either cached or pinned will not get deleted.
@@ -2511,7 +2511,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2545,7 +2545,7 @@ public class ShortcutService extends IShortcutService.Stub {
public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName,
@ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0;
final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0;
@@ -2575,7 +2575,7 @@ public class ShortcutService extends IShortcutService.Stub {
"getShareTargets");
final ComponentName chooser = injectChooserActivity();
final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2592,7 +2592,7 @@ public class ShortcutService extends IShortcutService.Stub {
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"hasShareTargets");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets();
@@ -2606,7 +2606,7 @@ public class ShortcutService extends IShortcutService.Stub {
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"isSharingShortcut");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(callingUserId);
@@ -2623,7 +2623,7 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> filter) {
@@ -2649,7 +2649,7 @@ public class ShortcutService extends IShortcutService.Stub {
final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
injectBinderCallingPid(), injectBinderCallingUid());
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2661,7 +2661,7 @@ public class ShortcutService extends IShortcutService.Stub {
public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
return getNextResetTimeLocked();
@@ -2672,7 +2672,7 @@ public class ShortcutService extends IShortcutService.Stub {
public int getIconMaxDimensions(String packageName, int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
return mMaxIconDimension;
}
}
@@ -2686,7 +2686,7 @@ public class ShortcutService extends IShortcutService.Stub {
shortcutId, packageName, userId));
}
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
@@ -2723,7 +2723,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
void resetThrottlingInner(@UserIdInt int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
Log.w(TAG, "User " + userId + " is locked or not running");
return;
@@ -2747,7 +2747,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId);
}
enforceResetThrottlingPermission();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
// This is called by system UI, so no need to throw. Just ignore.
return;
@@ -2804,7 +2804,7 @@ public class ShortcutService extends IShortcutService.Stub {
// even when hasShortcutPermission() is overridden.
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final String defaultLauncher = getDefaultLauncher(userId);
@@ -2830,7 +2830,7 @@ public class ShortcutService extends IShortcutService.Stub {
final long token = injectClearCallingIdentity();
boolean isSupported;
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
isSupported = !mUserManagerInternal.getUserProperties(userId)
.areItemsRestrictedOnHomeScreen();
}
@@ -2846,7 +2846,7 @@ public class ShortcutService extends IShortcutService.Stub {
final long start = getStatStartTime();
final long token = injectClearCallingIdentity();
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2890,7 +2890,7 @@ public class ShortcutService extends IShortcutService.Stub {
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
boolean appStillExists) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(user ->
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
appStillExists));
@@ -2904,7 +2904,7 @@ public class ShortcutService extends IShortcutService.Stub {
*
* This is called when an app is uninstalled, or an app gets "clear data"ed.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
boolean appStillExists) {
@@ -2979,7 +2979,7 @@ public class ShortcutService extends IShortcutService.Stub {
shortcutIds = null; // LauncherAppsService already threw for it though.
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3005,7 +3005,7 @@ public class ShortcutService extends IShortcutService.Stub {
return setReturnedByServer(ret);
}
- @GuardedBy("ShortcutService.this.mLock")
+ @GuardedBy("ShortcutService.this.mServiceLock")
private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable List<LocusId> locusIds, long changedSince,
@@ -3095,7 +3095,7 @@ public class ShortcutService extends IShortcutService.Stub {
return;
}
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
}
if (p == null) {
@@ -3129,7 +3129,7 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3143,7 +3143,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- @GuardedBy("ShortcutService.this.mLock")
+ @GuardedBy("ShortcutService.this.mServiceLock")
private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId,
@@ -3176,7 +3176,7 @@ public class ShortcutService extends IShortcutService.Stub {
throwIfUserLockedL(launcherUserId);
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
}
if (p == null) {
@@ -3198,7 +3198,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage sp;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3284,7 +3284,7 @@ public class ShortcutService extends IShortcutService.Stub {
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage sp;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3346,7 +3346,7 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3380,7 +3380,7 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
// Check in memory shortcut first
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3430,7 +3430,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3458,7 +3458,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3484,7 +3484,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3515,7 +3515,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Checks shortcuts in memory first
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3568,7 +3568,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3599,7 +3599,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(shortcutId, "shortcutId");
// Checks shortcuts in memory first
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3702,7 +3702,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (!callingPackage.equals(defaultLauncher)) {
return false;
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUidForegroundLocked(callingUid)) {
return false;
}
@@ -3733,7 +3733,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
scheduleSaveBaseState();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final long token = injectClearCallingIdentity();
try {
forEachLoadedUserLocked(user -> user.detectLocaleChange());
@@ -3762,7 +3762,7 @@ public class ShortcutService extends IShortcutService.Stub {
// but we still check it in unit tests.
final long token = injectClearCallingIdentity();
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
if (DEBUG) {
Slog.d(TAG, "Ignoring package broadcast " + action
@@ -3821,7 +3821,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems
// in odrder during saveToXml(), it could lead to shortcuts missing when shutdown.
// We need it so that it can finish up saving before shutdown.
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
mHandler.removeCallbacks(mSaveDirtyInfoRunner);
forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks);
@@ -3852,7 +3852,7 @@ public class ShortcutService extends IShortcutService.Stub {
try {
final ArrayList<UserPackage> gonePackages = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
// Find packages that have been uninstalled.
@@ -3885,7 +3885,7 @@ public class ShortcutService extends IShortcutService.Stub {
verifyStates();
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
if (DEBUG_REBOOT) {
Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime);
@@ -3916,7 +3916,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
@@ -3929,7 +3929,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
packageName, userId));
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
@@ -3972,7 +3972,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
// Activities may be disabled or enabled. Just rescan the package.
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(packageUserId);
user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
@@ -4474,7 +4474,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG) {
Slog.d(TAG, "Backing up user " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
wtf("Can't backup: user " + userId + " is locked or not running");
return null;
@@ -4524,7 +4524,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Restoring user " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
wtf("Can't restore: user " + userId + " is locked or not running");
return;
@@ -4762,7 +4762,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void dumpInner(PrintWriter pw, DumpFilter filter) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (filter.shouldDumpDetails()) {
final long now = injectCurrentTimeMillis();
pw.print("Now: [");
@@ -4841,7 +4841,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void dumpUid(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)");
for (int i = 0; i < mUidState.size(); i++) {
@@ -4876,7 +4876,7 @@ public class ShortcutService extends IShortcutService.Stub {
* behavior but shortcut service doesn't for now.
*/
private void dumpCheckin(PrintWriter pw, boolean clear) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
try {
final JSONArray users = new JSONArray();
@@ -4898,7 +4898,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void dumpDumpFiles(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)");
mShortcutDumpFiles.dumpAll(pw);
}
@@ -5051,7 +5051,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void handleResetThrottling() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
@@ -5071,7 +5071,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!updateConfigurationLocked(config)) {
throw new CommandException("override-config failed. See logcat for details.");
}
@@ -5081,7 +5081,7 @@ public class ShortcutService extends IShortcutService.Stub {
private void handleResetConfig() {
Slog.i(TAG, "cmd: handleResetConfig");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
loadConfigurationLocked();
}
}
@@ -5090,7 +5090,7 @@ public class ShortcutService extends IShortcutService.Stub {
// should query this information directly from RoleManager instead. Keeping the old behavior
// by returning the result from package manager.
private void handleGetDefaultLauncher() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String defaultLauncher = getDefaultLauncher(mUserId);
@@ -5114,7 +5114,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void handleUnloadUser() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
@@ -5124,7 +5124,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void handleClearShortcuts() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5136,7 +5136,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void handleGetShortcuts() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5162,7 +5162,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void handleHasShortcutAccess() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5318,7 +5318,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
@@ -5328,7 +5328,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return null;
@@ -5339,7 +5339,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
Consumer<ShortcutInfo> cb) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return;
cb.accept(pkg.findShortcutById(shortcutId));
@@ -5348,7 +5348,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
@@ -5385,14 +5385,14 @@ public class ShortcutService extends IShortcutService.Stub {
}
private void verifyStatesInner() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
}
}
@VisibleForTesting
void waitForBitmapSavesForTest() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(u ->
u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves));
}
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 2d76c5092b92..4ad4353f602e 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -38,6 +38,7 @@ import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +67,7 @@ public class SideFpsEventHandler implements View.OnClickListener {
private final int mDismissDialogTimeout;
@Nullable
private SideFpsToast mDialog;
+ private final AccessibilityManager mAccessibilityManager;
private final Runnable mTurnOffDialog =
() -> {
dismissDialog("mTurnOffDialog");
@@ -96,6 +98,7 @@ public class SideFpsEventHandler implements View.OnClickListener {
DialogProvider provider) {
mContext = context;
mHandler = handler;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mPowerManager = powerManager;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
@@ -157,7 +160,9 @@ public class SideFpsEventHandler implements View.OnClickListener {
mHandler.removeCallbacks(mTurnOffDialog);
}
showDialog(eventTime, "Enroll Power Press");
- mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+ if (!mAccessibilityManager.isEnabled()) {
+ mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+ }
});
return true;
case STATE_BP_AUTH:
@@ -231,6 +236,10 @@ public class SideFpsEventHandler implements View.OnClickListener {
public void onBiometricAction(
@BiometricStateListener.Action int action) {
Log.d(TAG, "onBiometricAction " + action);
+ if (mAccessibilityManager != null
+ && mAccessibilityManager.isEnabled()) {
+ dismissDialog("mTurnOffDialog");
+ }
}
});
mSideFpsEventHandlerReady.set(true);
@@ -256,6 +265,9 @@ public class SideFpsEventHandler implements View.OnClickListener {
mLastPowerPressTime = time;
mDialog.show();
mDialog.setOnClickListener(this);
+ if (mAccessibilityManager.isEnabled()) {
+ mDialog.addAccessibilityDelegate();
+ }
}
interface DialogProvider {
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
index db074670de9b..c27753ce1c0f 100644
--- a/services/core/java/com/android/server/policy/SideFpsToast.java
+++ b/services/core/java/com/android/server/policy/SideFpsToast.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -23,6 +24,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import com.android.internal.R;
@@ -34,7 +36,6 @@ import com.android.internal.R;
* This dialog is used by {@link SideFpsEventHandler}
*/
public class SideFpsToast extends Dialog {
-
SideFpsToast(Context context) {
super(context);
}
@@ -66,4 +67,27 @@ public class SideFpsToast extends Dialog {
turnOffScreen.setOnClickListener(listener);
}
}
+
+ /**
+ * When accessibility mode is on, add AccessibilityDelegate to dismiss dialog when focus is
+ * moved away from the dialog.
+ */
+ public void addAccessibilityDelegate() {
+ final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+ if (turnOffScreen != null) {
+ turnOffScreen.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityEvent(@NonNull View host,
+ @NonNull AccessibilityEvent event) {
+ if (event.getEventType()
+ == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ && isShowing()) {
+ dismiss();
+ }
+ super.onInitializeAccessibilityEvent(host, event);
+ }
+ });
+
+ }
+ }
}
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 592d0396b349..12db21dbe41a 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -40,6 +40,7 @@ import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Objects;
/**
@@ -211,10 +212,6 @@ public abstract class AnrTimer<V> implements AutoCloseable {
@GuardedBy("mLock")
private int mTotalStarted = 0;
- /** The total number of timers that were restarted without an explicit cancel. */
- @GuardedBy("mLock")
- private int mTotalRestarted = 0;
-
/** The total number of errors detected. */
@GuardedBy("mLock")
private int mTotalErrors = 0;
@@ -368,7 +365,7 @@ public abstract class AnrTimer<V> implements AutoCloseable {
abstract boolean enabled();
- abstract void dump(PrintWriter pw, boolean verbose);
+ abstract void dump(IndentingPrintWriter pw, boolean verbose);
abstract void close();
}
@@ -410,9 +407,14 @@ public abstract class AnrTimer<V> implements AutoCloseable {
return false;
}
- /** dump() is a no-op when the feature is disabled. */
+ /** Dump the limited statistics captured when the feature is disabled. */
@Override
- void dump(PrintWriter pw, boolean verbose) {
+ void dump(IndentingPrintWriter pw, boolean verbose) {
+ synchronized (mLock) {
+ pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
+ mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+ mTotalExpired, mTotalErrors);
+ }
}
/** close() is a no-op when the feature is disabled. */
@@ -441,6 +443,10 @@ public abstract class AnrTimer<V> implements AutoCloseable {
*/
private long mNative = 0;
+ /** The total number of timers that were restarted without an explicit cancel. */
+ @GuardedBy("mLock")
+ private int mTotalRestarted = 0;
+
/** Fetch the native tag (an integer) for the given label. */
FeatureEnabled() {
mNative = nativeAnrTimerCreate(mLabel);
@@ -537,13 +543,22 @@ public abstract class AnrTimer<V> implements AutoCloseable {
/** Dump statistics from the native layer. */
@Override
- void dump(PrintWriter pw, boolean verbose) {
+ void dump(IndentingPrintWriter pw, boolean verbose) {
synchronized (mLock) {
- if (mNative != 0) {
- nativeAnrTimerDump(mNative, verbose);
- } else {
+ if (mNative == 0) {
pw.println("closed");
+ return;
}
+ String[] nativeDump = nativeAnrTimerDump(mNative);
+ if (nativeDump == null) {
+ pw.println("no-data");
+ return;
+ }
+ for (String s : nativeDump) {
+ pw.println(s);
+ }
+ // The following counter is only available at the Java level.
+ pw.println("restarted:" + mTotalRestarted);
}
}
@@ -690,11 +705,8 @@ public abstract class AnrTimer<V> implements AutoCloseable {
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n",
- mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
- mTotalExpired, mTotalErrors);
- pw.decreaseIndent();
mFeature.dump(pw, false);
+ pw.decreaseIndent();
}
}
@@ -755,6 +767,14 @@ public abstract class AnrTimer<V> implements AutoCloseable {
recordErrorLocked(operation, "notFound", arg);
}
+ /** Compare two AnrTimers in display order. */
+ private static final Comparator<AnrTimer> sComparator =
+ Comparator.nullsLast(new Comparator<>() {
+ @Override
+ public int compare(AnrTimer o1, AnrTimer o2) {
+ return o1.mLabel.compareTo(o2.mLabel);
+ }});
+
/** Dumpsys output, allowing for overrides. */
@VisibleForTesting
static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
@@ -764,11 +784,18 @@ public abstract class AnrTimer<V> implements AutoCloseable {
ipw.println("AnrTimer statistics");
ipw.increaseIndent();
synchronized (sAnrTimerList) {
+ // Find the currently live instances and sort them by their label. The goal is to
+ // have consistent output ordering.
final int size = sAnrTimerList.size();
- ipw.println("reporting " + size + " timers");
+ AnrTimer[] active = new AnrTimer[size];
+ int valid = 0;
for (int i = 0; i < size; i++) {
AnrTimer a = sAnrTimerList.valueAt(i).get();
- if (a != null) a.dump(ipw);
+ if (a != null) active[valid++] = a;
+ }
+ Arrays.sort(active, 0, valid, sComparator);
+ for (int i = 0; i < valid; i++) {
+ if (active[i] != null) active[i].dump(ipw);
}
}
if (verbose) dumpErrors(ipw);
@@ -827,6 +854,6 @@ public abstract class AnrTimer<V> implements AutoCloseable {
/** Discard an expired timer by ID. Return true if the timer was found. */
private static native boolean nativeAnrTimerDiscard(long service, int timerId);
- /** Prod the native library to log a few statistics. */
- private static native void nativeAnrTimerDump(long service, boolean verbose);
+ /** Retrieve runtime dump information from the native layer. */
+ private static native String[] nativeAnrTimerDump(long service);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1f320dab99dd..7d057a9d3aca 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9817,10 +9817,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
return mLetterboxUiController.getUserMinAspectRatio();
}
- if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
+ if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
+ && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
return info.getMinAspectRatio();
}
-
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
&& !ActivityInfo.isFixedOrientationPortrait(
getOverrideOrientation())) {
@@ -9941,6 +9941,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return updateReportedConfigurationAndSend();
}
+ /**
+ * @return {@code true} if the Camera is active for the current activity
+ */
+ boolean isCameraActive() {
+ return mDisplayContent != null
+ && mDisplayContent.getDisplayRotationCompatPolicy() != null
+ && mDisplayContent.getDisplayRotationCompatPolicy()
+ .isCameraActive(this, /* mustBeFullscreen */ true);
+ }
+
boolean updateReportedConfigurationAndSend() {
if (isConfigurationDispatchPaused()) {
Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 8069a9335151..1c599777e497 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -48,15 +48,8 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
/**
* Flag to indicate whether to restrict desktop mode to supported devices.
*/
- @VisibleForTesting
- static final String ENFORCE_DEVICE_RESTRICTIONS_KEY =
- "persist.wm.debug.desktop_mode_enforce_device_restrictions";
-
- /**
- * Flag to indicate whether to restrict desktop mode to supported devices.
- */
private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
- ENFORCE_DEVICE_RESTRICTIONS_KEY, true);
+ "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
private StringBuilder mLogBuilder;
@@ -118,19 +111,7 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
}
if (phase == PHASE_WINDOWING_MODE) {
- return RESULT_DONE;
- }
-
- // TODO(b/336998072) - Find a better solution to this that makes use of the logic from
- // TaskLaunchParamsModifier. Put logic in common utils, return RESULT_CONTINUE, inherit
- // from parent class, etc.
- if (outParams.mPreferredTaskDisplayArea == null && task.getRootTask() != null) {
- appendLog("display-from-task=" + task.getRootTask().getDisplayId());
- outParams.mPreferredTaskDisplayArea = task.getRootTask().getDisplayArea();
- }
-
- if (phase == PHASE_DISPLAY_AREA) {
- return RESULT_DONE;
+ return RESULT_CONTINUE;
}
if (!currentParams.mBounds.isEmpty()) {
@@ -142,7 +123,7 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
appendLog("setting desktop mode task bounds to %s", outParams.mBounds);
- return RESULT_DONE;
+ return RESULT_CONTINUE;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f7e5dd8632fc..da2999810a10 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1318,6 +1318,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ /**
+ * @return The {@link DisplayRotationCompatPolicy} for this DisplayContent
+ */
+ // TODO(b/335387481) Allow access to DisplayRotationCompatPolicy only with getters
+ @Nullable
+ DisplayRotationCompatPolicy getDisplayRotationCompatPolicy() {
+ return mDisplayRotationCompatPolicy;
+ }
+
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index d5f8df35d96f..eacf9a3fa759 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -64,7 +64,7 @@ import com.android.server.UiThread;
* R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
*/
// TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
// Delay for updating display rotation after Camera connection is closed. Needed to avoid
// rotation flickering when an app is flipping between front and rear cameras or when size
@@ -306,7 +306,8 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
- && isCameraActive(activity, /* mustBeFullscreen */ true);
+ && isCameraActive(activity, /* mustBeFullscreen */ true)
+ && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
@@ -324,6 +325,13 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
}
+ boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ // Checking windowing mode on activity level because we don't want to
+ // apply treatment in case of activity embedding.
+ return (!mustBeFullscreen || !activity.inMultiWindowMode())
+ && mCameraStateMonitor.isCameraRunningForActivity(activity);
+ }
+
private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
boolean mustBeFullscreen) {
return activity != null && isCameraActive(activity, mustBeFullscreen)
@@ -331,14 +339,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
- && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
- }
-
- private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
- // Checking windowing mode on activity level because we don't want to
- // apply treatment in case of activity embedding.
- return (!mustBeFullscreen || !activity.inMultiWindowMode())
- && mCameraStateMonitor.isCameraRunningForActivity(activity)
+ && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 683efde4bb09..b38e666ea691 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -30,6 +30,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIE
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
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_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
@@ -510,6 +511,26 @@ final class LetterboxUiController {
}
/**
+ * Whether we should apply the min aspect ratio per-app override only when an app is connected
+ * to the camera.
+ * When this override is applied the min aspect ratio given in the app's manifest will be
+ * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
+ * is higher. The treatment will also apply if no value is provided in the manifest.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideMinAspectRatioForCamera() {
+ return mActivityRecord.isCameraActive()
+ && mAllowMinAspectRatioOverrideOptProp
+ .shouldEnableWithOptInOverrideAndOptOutProperty(
+ isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+ }
+
+ /**
* Whether we should apply the force resize per-app override. When this override is applied it
* forces the packages it is applied to to be resizable. It won't change whether the app can be
* put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -962,7 +983,8 @@ final class LetterboxUiController {
void recomputeConfigurationForCameraCompatIfNeeded() {
if (isOverrideOrientationOnlyForCameraEnabled()
- || isCameraCompatSplitScreenAspectRatioAllowed()) {
+ || isCameraCompatSplitScreenAspectRatioAllowed()
+ || shouldOverrideMinAspectRatioForCamera()) {
mActivityRecord.recomputeConfiguration();
}
}
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index da95666fb7c4..6509958defda 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -161,14 +161,14 @@ class AnrTimerService {
// A timer has expired.
void expire(timer_id_t);
- // Dump a small amount of state to the log file.
- void dump(bool verbose) const;
-
// Return the Java object associated with this instance.
jweak jtimer() const {
return notifierObject_;
}
+ // Return the per-instance statistics.
+ std::vector<std::string> getDump() const;
+
private:
// The service cannot be copied.
AnrTimerService(AnrTimerService const &) = delete;
@@ -199,7 +199,7 @@ class AnrTimerService {
std::set<Timer> running_;
// The maximum number of active timers.
- size_t maxActive_;
+ size_t maxRunning_;
// Simple counters
struct Counters {
@@ -209,6 +209,7 @@ class AnrTimerService {
size_t accepted;
size_t discarded;
size_t expired;
+ size_t extended;
// The number of times there were zero active timers.
size_t drained;
@@ -437,7 +438,9 @@ class AnrTimerService::Ticker {
// Construct the ticker. This creates the timerfd file descriptor and starts the monitor
// thread. The monitor thread is given a unique name.
- Ticker() {
+ Ticker() :
+ id_(idGen_.fetch_add(1))
+ {
timerFd_ = timer_create();
if (timerFd_ < 0) {
ALOGE("failed to create timerFd: %s", strerror(errno));
@@ -502,6 +505,11 @@ class AnrTimerService::Ticker {
}
}
+ // The unique ID of this particular ticker. Used for debug and logging.
+ size_t id() const {
+ return id_;
+ }
+
// Return the number of timers still running.
size_t running() const {
AutoMutex _l(lock_);
@@ -617,8 +625,16 @@ class AnrTimerService::Ticker {
// The list of timers that are scheduled. This set is sorted by timeout and then by timer
// ID. A set is sufficient (as opposed to a multiset) because timer IDs are unique.
std::set<Entry> running_;
+
+ // A unique ID assigned to this instance.
+ const size_t id_;
+
+ // The ID generator.
+ static std::atomic<size_t> idGen_;
};
+std::atomic<size_t> AnrTimerService::Ticker::idGen_;
+
AnrTimerService::AnrTimerService(char const* label,
notifier_t notifier, void* cookie, jweak jtimer, Ticker* ticker) :
@@ -629,7 +645,7 @@ AnrTimerService::AnrTimerService(char const* label,
ticker_(ticker) {
// Zero the statistics
- maxActive_ = 0;
+ maxRunning_ = 0;
memset(&counters_, 0, sizeof(counters_));
ALOGI_IF(DEBUG, "initialized %s", label);
@@ -739,6 +755,12 @@ void AnrTimerService::expire(timer_id_t timerId) {
elapsed = now() - t.started;
}
+ if (expired) {
+ counters_.expired++;
+ } else {
+ counters_.extended++;
+ }
+
// Deliver the notification outside of the lock.
if (expired) {
if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
@@ -756,8 +778,8 @@ void AnrTimerService::insert(const Timer& t) {
if (t.status == Running) {
// Only forward running timers to the ticker. Expired timers are handled separately.
ticker_->insert(t.scheduled, t.id, this);
- maxActive_ = std::max(maxActive_, running_.size());
}
+ maxRunning_ = std::max(maxRunning_, running_.size());
}
AnrTimerService::Timer AnrTimerService::remove(timer_id_t timerId) {
@@ -767,29 +789,32 @@ AnrTimerService::Timer AnrTimerService::remove(timer_id_t timerId) {
Timer result = *found;
running_.erase(found);
ticker_->remove(result.scheduled, result.id);
+ if (running_.size() == 0) counters_.drained++;
return result;
}
return Timer();
}
-void AnrTimerService::dump(bool verbose) const {
+std::vector<std::string> AnrTimerService::getDump() const {
+ std::vector<std::string> r;
AutoMutex _l(lock_);
- ALOGI("timer %s ops started=%zu canceled=%zu accepted=%zu discarded=%zu expired=%zu",
- label_.c_str(),
- counters_.started, counters_.canceled, counters_.accepted,
- counters_.discarded, counters_.expired);
- ALOGI("timer %s stats max-active=%zu/%zu running=%zu/%zu errors=%zu",
- label_.c_str(),
- maxActive_, ticker_->maxRunning(), running_.size(), ticker_->running(),
- counters_.error);
-
- if (verbose) {
- nsecs_t time = now();
- for (auto i = running_.begin(); i != running_.end(); i++) {
- Timer t = *i;
- ALOGI(" running %s", t.toString(time).c_str());
- }
- }
+ r.push_back(StringPrintf("started:%zu canceled:%zu accepted:%zu discarded:%zu expired:%zu",
+ counters_.started,
+ counters_.canceled,
+ counters_.accepted,
+ counters_.discarded,
+ counters_.expired));
+ r.push_back(StringPrintf("extended:%zu drained:%zu error:%zu running:%zu maxRunning:%zu",
+ counters_.extended,
+ counters_.drained,
+ counters_.error,
+ running_.size(),
+ maxRunning_));
+ r.push_back(StringPrintf("ticker:%zu ticking:%zu maxTicking:%zu",
+ ticker_->id(),
+ ticker_->running(),
+ ticker_->maxRunning()));
+ return r;
}
/**
@@ -894,21 +919,26 @@ jboolean anrTimerDiscard(JNIEnv* env, jclass, jlong ptr, jint timerId) {
return toService(ptr)->discard(timerId);
}
-jint anrTimerDump(JNIEnv *env, jclass, jlong ptr, jboolean verbose) {
- if (!nativeSupportEnabled) return -1;
- toService(ptr)->dump(verbose);
- return 0;
+jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) {
+ if (!nativeSupportEnabled) return nullptr;
+ std::vector<std::string> stats = toService(ptr)->getDump();
+ jclass sclass = env->FindClass("java/lang/String");
+ jobjectArray r = env->NewObjectArray(stats.size(), sclass, nullptr);
+ for (size_t i = 0; i < stats.size(); i++) {
+ env->SetObjectArrayElement(r, i, env->NewStringUTF(stats[i].c_str()));
+ }
+ return r;
}
static const JNINativeMethod methods[] = {
{"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
- {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
- {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
- {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
- {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
- {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
- {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
- {"nativeAnrTimerDump", "(JZ)V", (void*) anrTimerDump},
+ {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
+ {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
+ {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
+ {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
+ {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
+ {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
+ {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
};
} // anonymous namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ff757366ea23..85ab562902e2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5673,18 +5673,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
int flags, CallerIdentity caller) {
- final boolean isPin = PasswordMetrics.isNumericOnly(password);
- try (LockscreenCredential newCredential =
- isPin ? LockscreenCredential.createPin(password) :
- LockscreenCredential.createPasswordOrNone(password)) {
- return resetPasswordInternal(newCredential, tokenHandle, token, flags, caller);
- }
- }
-
- private boolean resetPasswordInternal(LockscreenCredential newCredential,
- long tokenHandle, byte[] token, int flags, CallerIdentity caller) {
final int callingUid = caller.getUid();
final int userHandle = UserHandle.getUserId(callingUid);
+ final boolean isPin = PasswordMetrics.isNumericOnly(password);
+ final LockscreenCredential newCredential;
+ if (isPin) {
+ newCredential = LockscreenCredential.createPin(password);
+ } else {
+ newCredential = LockscreenCredential.createPasswordOrNone(password);
+ }
synchronized (getLockObject()) {
final PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userHandle);
final int complexity = getAggregatedPasswordComplexityLocked(userHandle);
@@ -21613,7 +21610,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
== HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
}
- if (Flags.headlessSingleUserFixes() && isSingleUserMode && !mInjector.isChangeEnabled(
+ if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
+ && isSingleUserMode && !mInjector.isChangeEnabled(
PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
throw new IllegalStateException("Device admin is not targeting Android V.");
}
@@ -21632,7 +21630,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setLocale(provisioningParams.getLocale());
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && isSingleUserMode
+ && isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
new file mode 100644
index 000000000000..f0abcd228f14
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.mode
+
+import android.content.Context
+import android.util.SparseArray
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
+import com.android.server.display.mode.RefreshRateVote.RenderVote
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class AppRequestObserverTest {
+
+ private lateinit var context: Context
+ private val mockInjector = mock<DisplayModeDirector.Injector>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockDisplayDeviceConfigProvider = mock<DisplayDeviceConfigProvider>()
+ private val testHandler = TestHandler(null)
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @Test
+ fun `test app request votes`(@TestParameter testCase: AppRequestTestCase) {
+ whenever(mockFlags.ignoreAppPreferredRefreshRateRequest())
+ .thenReturn(testCase.ignoreRefreshRateRequest)
+ val displayModeDirector = DisplayModeDirector(
+ context, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
+ val modes = arrayOf(
+ Display.Mode(1, 1000, 1000, 60f),
+ Display.Mode(2, 1000, 1000, 90f),
+ Display.Mode(3, 1000, 1000, 120f)
+ )
+ displayModeDirector.injectSupportedModesByDisplay(SparseArray<Array<Display.Mode>>().apply {
+ append(Display.DEFAULT_DISPLAY, modes)
+ })
+ displayModeDirector.injectDefaultModeByDisplay(SparseArray<Display.Mode>().apply {
+ append(Display.DEFAULT_DISPLAY, modes[0])
+ })
+
+ displayModeDirector.appRequestObserver.setAppRequest(Display.DEFAULT_DISPLAY,
+ testCase.modeId,
+ testCase.requestedRefreshRates,
+ testCase.requestedMinRefreshRates,
+ testCase.requestedMaxRefreshRates)
+
+ val baseModeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE)
+ assertThat(baseModeVote).isEqualTo(testCase.expectedBaseModeVote)
+
+ val sizeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_SIZE)
+ assertThat(sizeVote).isEqualTo(testCase.expectedSizeVote)
+
+ val renderRateVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE)
+ assertThat(renderRateVote).isEqualTo(testCase.expectedRenderRateVote)
+ }
+
+ enum class AppRequestTestCase(
+ val ignoreRefreshRateRequest: Boolean,
+ val modeId: Int,
+ val requestedRefreshRates: Float,
+ val requestedMinRefreshRates: Float,
+ val requestedMaxRefreshRates: Float,
+ internal val expectedBaseModeVote: Vote?,
+ internal val expectedSizeVote: Vote?,
+ internal val expectedRenderRateVote: Vote?,
+ ) {
+ BASE_MODE_60(true, 1, 0f, 0f, 0f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
+ BASE_MODE_90(true, 2, 0f, 0f, 0f,
+ BaseModeRefreshRateVote(90f), SizeVote(1000, 1000, 1000, 1000), null),
+ MIN_REFRESH_RATE_60(true, 0, 0f, 60f, 0f,
+ null, null, RenderVote(60f, Float.POSITIVE_INFINITY)),
+ MIN_REFRESH_RATE_120(true, 0, 0f, 120f, 0f,
+ null, null, RenderVote(120f, Float.POSITIVE_INFINITY)),
+ MAX_REFRESH_RATE_60(true, 0, 0f, 0f, 60f,
+ null, null, RenderVote(0f, 60f)),
+ MAX_REFRESH_RATE_120(true, 0, 0f, 0f, 120f,
+ null, null, RenderVote(0f, 120f)),
+ INVALID_MIN_MAX_REFRESH_RATE(true, 0, 0f, 90f, 60f,
+ null, null, null),
+ BASE_MODE_MIN_MAX(true, 1, 0f, 60f, 90f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), RenderVote(60f, 90f)),
+ PREFERRED_REFRESH_RATE(false, 0, 60f, 0f, 0f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
+ PREFERRED_REFRESH_RATE_IGNORED(true, 0, 60f, 0f, 0f,
+ null, null, null),
+ PREFERRED_REFRESH_RATE_INVALID(false, 0, 45f, 0f, 0f,
+ null, null, null),
+ }
+} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 52e157b7788f..cd1e9e85afb5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -31,7 +31,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -2262,162 +2261,6 @@ public class DisplayModeDirectorTest {
}
@Test
- public void testAppRequestObserver_modeId() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- SizeVote sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(vote);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(vote);
- }
-
- @Test
- public void testAppRequestObserver_minRefreshRate() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
- assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0);
- appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
- }
-
- @Test
- public void testAppRequestObserver_maxRefreshRate() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isZero();
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60);
- appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isZero();
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
- }
-
- @Test
- public void testAppRequestObserver_invalidRefreshRateRange() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 60);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote appRequestRefreshRateRange =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(appRequestRefreshRateRange);
- }
-
- @Test
- public void testAppRequestObserver_modeIdAndRefreshRateRange() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- SizeVote sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- }
-
- @Test
public void testAppRequestsIsTheDefaultMode() {
Display.Mode[] modes = new Display.Mode[2];
modes[0] = new Display.Mode(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 3e0677c62f1c..4d910cefdb79 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -146,8 +146,8 @@ class SettingsObserverTest {
val modes = arrayOf(
Display.Mode(1, 1000, 1000, 60f),
- Display.Mode(1, 1000, 1000, 90f),
- Display.Mode(1, 1000, 1000, 120f)
+ Display.Mode(2, 1000, 1000, 90f),
+ Display.Mode(3, 1000, 1000, 120f)
)
displayModeDirector.injectSupportedModesByDisplay(SparseArray<Array<Display.Mode>>().apply {
append(Display.DEFAULT_DISPLAY, modes)
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 23314cdaf041..1322545c8d7e 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -20,12 +20,17 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
import android.os.Looper;
import android.platform.test.annotations.EnableFlags;
import android.service.dreams.DreamService;
@@ -41,6 +46,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -83,6 +89,18 @@ public class DreamServiceTest {
assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
}
+ @Test
+ public void testMetadataParsing_exceptionReading() {
+ final PackageManager packageManager = Mockito.mock(PackageManager.class);
+ final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
+ final TypedArray rawMetadata = Mockito.mock(TypedArray.class);
+ when(packageManager.extractPackageItemInfoAttributes(eq(serviceInfo), any(), any(), any()))
+ .thenReturn(rawMetadata);
+ when(rawMetadata.getString(anyInt())).thenThrow(new RuntimeException("failure"));
+
+ assertThat(DreamService.getDreamMetadata(packageManager, serviceInfo)).isNull();
+ }
+
private DreamService.DreamMetadata getDreamMetadata(String dreamClassName)
throws PackageManager.NameNotFoundException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index a8b792e30485..80f7a066985b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -197,7 +197,7 @@ public class AsyncProcessStartTest {
+ Arrays.toString(invocation.getArguments()));
if (!wedge) {
if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
- mRealAms.finishAttachApplication(0);
+ mRealAms.finishAttachApplication(0, 0);
}
}
return null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 67be93b3b49f..89c67d5e32b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -200,7 +200,7 @@ public class ProcessObserverTest {
Log.v(TAG, "Intercepting bindApplication() for "
+ Arrays.toString(invocation.getArguments()));
if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
- mRealAms.finishAttachApplication(0);
+ mRealAms.finishAttachApplication(0, 0);
}
return null;
}).when(thread).bindApplication(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index e672928c5403..599b9cd06ee1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -24,6 +24,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import android.content.ContentResolver;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -42,6 +43,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
/**
* Test SettingsToPropertiesMapper.
@@ -61,6 +63,7 @@ public class SettingsToPropertiesMapperTest {
private HashMap<String, String> mSystemSettingsMap;
private HashMap<String, String> mGlobalSettingsMap;
+ private HashMap<String, String> mConfigSettingsMap;
@Before
public void setUp() throws Exception {
@@ -71,9 +74,11 @@ public class SettingsToPropertiesMapperTest {
.spyStatic(SystemProperties.class)
.spyStatic(Settings.Global.class)
.spyStatic(SettingsToPropertiesMapper.class)
+ .spyStatic(Settings.Config.class)
.startMocking();
mSystemSettingsMap = new HashMap<>();
mGlobalSettingsMap = new HashMap<>();
+ mConfigSettingsMap = new HashMap<>();
// Mock SystemProperties setter and various getters
doAnswer((Answer<Void>) invocationOnMock -> {
@@ -93,7 +98,7 @@ public class SettingsToPropertiesMapperTest {
}
).when(() -> SystemProperties.get(anyString()));
- // Mock Settings.Global methods
+ // Mock Settings.Global method
doAnswer((Answer<String>) invocationOnMock -> {
String key = invocationOnMock.getArgument(1);
@@ -101,6 +106,21 @@ public class SettingsToPropertiesMapperTest {
}
).when(() -> Settings.Global.getString(any(), anyString()));
+ // Mock Settings.Config getstrings method
+ doAnswer((Answer<Map<String, String>>) invocationOnMock -> {
+ String namespace = invocationOnMock.getArgument(0);
+ List<String> flags = invocationOnMock.getArgument(1);
+ HashMap<String, String> values = new HashMap<>();
+ for (String flag : flags) {
+ String value = mConfigSettingsMap.get(namespace + "/" + flag);
+ if (value != null) {
+ values.put(flag, value);
+ }
+ }
+ return values;
+ }
+ ).when(() -> Settings.Config.getStrings(anyString(), any()));
+
mTestMapper = new SettingsToPropertiesMapper(
mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {});
}
@@ -239,4 +259,43 @@ public class SettingsToPropertiesMapperTest {
Assert.assertTrue(categories.contains("category2"));
Assert.assertTrue(categories.contains("category3"));
}
+
+ @Test
+ public void testGetStagedFlagsWithValueChange() {
+ // mock up what is in the setting already
+ mConfigSettingsMap.put("namespace_1/flag_1", "true");
+ mConfigSettingsMap.put("namespace_1/flag_2", "true");
+
+ // mock up input
+ String namespace = "staged";
+ Map<String, String> keyValueMap = new HashMap<>();
+ // case 1: existing prop, stage the same value
+ keyValueMap.put("namespace_1*flag_1", "true");
+ // case 2: existing prop, stage a different value
+ keyValueMap.put("namespace_1*flag_2", "false");
+ // case 3: new prop, stage the non default value
+ keyValueMap.put("namespace_2*flag_1", "true");
+ // case 4: new prop, stage the default value
+ keyValueMap.put("namespace_2*flag_2", "false");
+ Properties props = new Properties(namespace, keyValueMap);
+
+ HashMap<String, HashMap<String, String>> toStageProps =
+ SettingsToPropertiesMapper.getStagedFlagsWithValueChange(props);
+
+ HashMap<String, String> namespace_1_to_stage = toStageProps.get("namespace_1");
+ HashMap<String, String> namespace_2_to_stage = toStageProps.get("namespace_2");
+ Assert.assertTrue(namespace_1_to_stage != null);
+ Assert.assertTrue(namespace_2_to_stage != null);
+
+ String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
+ String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
+ String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
+ String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2");
+ Assert.assertTrue(namespace_1_flag_1 == null);
+ Assert.assertTrue(namespace_1_flag_2 != null);
+ Assert.assertTrue(namespace_2_flag_1 != null);
+ Assert.assertTrue(namespace_2_flag_2 == null);
+ Assert.assertTrue(namespace_1_flag_2.equals("false"));
+ Assert.assertTrue(namespace_2_flag_1.equals("true"));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index a6f2196cf05b..9862663c37b2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
package com.android.server.os;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -25,9 +23,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
-import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -61,6 +59,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -104,7 +104,7 @@ public class BugreportManagerServiceImplTest {
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager);
+ mMockUserManager, mMockDevicePolicyManager, null);
mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
@@ -114,24 +114,8 @@ public class BugreportManagerServiceImplTest {
@After
public void tearDown() throws Exception {
- // Changes to RoleManager persist between tests, so we need to clear out any funny
- // business we did in previous tests.
+ // Clean up the mapping file between tests since it would otherwise persist.
mMappingFile.delete();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(false);
- roleManager.removeRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
}
@Test
@@ -267,7 +251,10 @@ public class BugreportManagerServiceImplTest {
@Test
public void testCancelBugreportWithoutRole() {
- clearAllowlist();
+ // Create a new service to clear the allowlist
+ mService = new BugreportManagerServiceImpl(
+ new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager, null));
assertThrows(SecurityException.class, () -> mService.cancelBugreport(
Binder.getCallingUid(), mContext.getPackageName()));
@@ -275,29 +262,13 @@ public class BugreportManagerServiceImplTest {
@Test
public void testCancelBugreportWithRole() throws Exception {
- clearAllowlist();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
- mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
- }
-
- private void clearAllowlist() {
+ // Create a new service to clear the allowlist, but override the role manager
mService = new BugreportManagerServiceImpl(
new TestInjector(mContext, new ArraySet<>(), mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mMockUserManager, mMockDevicePolicyManager,
+ "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"));
+
+ mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
}
private static class Listener implements IDumpstateListener {
@@ -359,10 +330,22 @@ public class BugreportManagerServiceImplTest {
private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
- UserManager um, DevicePolicyManager dpm) {
+ UserManager um, DevicePolicyManager dpm, String grantedRole) {
super(context, allowlistedPackages, mappingFile);
mUserManager = um;
mDevicePolicyManager = dpm;
+
+ if (grantedRole != null) {
+ mRoleManagerWrapper =
+ new BugreportManagerServiceImpl.Injector.RoleManagerWrapper() {
+ @Override
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return roleName.equals(grantedRole)
+ ? Collections.singletonList(mContext.getPackageName())
+ : Collections.emptyList();
+ }
+ };
+ }
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 669eedf39342..dabf5317f168 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -31,11 +31,13 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.test.TestLooper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableResources;
import android.view.Window;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -48,7 +50,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.List;
@@ -72,9 +75,15 @@ public class SideFpsEventHandlerTest {
private static final Integer AUTO_DISMISS_DIALOG = 500;
@Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
+ private final AccessibilityManager mAccessibilityManager =
+ mContext.getSystemService(AccessibilityManager.class);
+
@Mock
private PackageManager mPackageManager;
@Mock
@@ -89,9 +98,8 @@ public class SideFpsEventHandlerTest {
private BiometricStateListener mBiometricStateListener;
@Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
+ public void setup() throws RemoteException {
+ disableAccessibility();
mContext.addMockSystemService(PackageManager.class, mPackageManager);
mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
TestableResources resources = mContext.getOrCreateTestableResources();
@@ -192,9 +200,8 @@ public class SideFpsEventHandlerTest {
}
@Test
- public void dialogDismissesAfterTime() throws Exception {
+ public void dialogDismissesAfterTime_accessibilityDisabled() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
-
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -207,9 +214,23 @@ public class SideFpsEventHandlerTest {
}
@Test
- public void dialogDoesNotDismissOnSensorTouch() throws Exception {
+ public void dialogDoesNotDismissAfterTime_accessibilityEnabled() throws Exception {
+ enableAccessibility();
setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mDialog).show();
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
+ mLooper.dispatchAll();
+ verify(mDialog, never()).dismiss();
+ }
+ @Test
+ public void dialogDoesNotDismissOnSensorTouch_accessibilityDisabled() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -218,12 +239,26 @@ public class SideFpsEventHandlerTest {
verify(mDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
mLooper.dispatchAll();
-
verify(mDialog, never()).dismiss();
}
+ @Test
+ public void dialogDismissesOnSensorTouch_accessibilityEnabled() throws Exception {
+ enableAccessibility();
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ mLooper.dispatchAll();
+ verify(mDialog).dismiss();
+ }
+
private void setBiometricState(@BiometricStateListener.State int newState) {
if (mBiometricStateListener != null) {
mBiometricStateListener.onStateChanged(newState);
@@ -231,6 +266,20 @@ public class SideFpsEventHandlerTest {
}
}
+ private void enableAccessibility() throws RemoteException {
+ if (mAccessibilityManager != null) {
+ mAccessibilityManager.getClient().setState(1);
+ mLooper.dispatchAll();
+ }
+ }
+
+ private void disableAccessibility() throws RemoteException {
+ if (mAccessibilityManager != null) {
+ mAccessibilityManager.getClient().setState(0);
+ mLooper.dispatchAll();
+ }
+ }
+
private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
.thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 0d6fdc9e312a..4af20a97a9aa 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2638,7 +2638,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
@Test
public void testSoundResetsRankingTime() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME);
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME);
TestableFlagResolver flagResolver = new TestableFlagResolver();
initAttentionHelper(flagResolver);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 74d8433700d3..3a0eba11d488 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,7 +24,7 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
-import static android.app.Flags.FLAG_UPDATE_RANKING_TIME;
+import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
@@ -7376,7 +7376,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME})
public void testVisualDifference_userInitiatedJob() {
Notification.Builder nb1 = new Notification.Builder(mContext, "")
.setContentTitle("foo");
@@ -15283,7 +15283,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_newNotification_noisy_matchesSbn() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, mUserId);
@@ -15297,7 +15297,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_newNotification_silent_matchesSbn() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
@@ -15312,7 +15312,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_silentSameText_originalPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
@@ -15332,7 +15332,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_silentNewText_newPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, 0, mUserId);
@@ -15357,7 +15357,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_noisySameText_newPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 9a58594e818c..d1880d2f3528 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1561,14 +1561,14 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
false, mClock);
- loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM);
+ loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
- when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_R);
mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
- new int[]{UID_P});
+ new int[]{UID_R});
- NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_R, id,
false);
assertThat(channel.getImportance()).isEqualTo(2);
assertThat(channel.canShowBadge()).isTrue();
@@ -1616,7 +1616,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
false, mClock);
- loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+ loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 527001df995f..9a6e81865947 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -378,7 +378,7 @@ public class RankingHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldWhenChildren_unspecifiedSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -430,7 +430,7 @@ public class RankingHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldChildren_unspecifiedSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -480,7 +480,7 @@ public class RankingHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldChildren_oldSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -517,10 +517,10 @@ public class RankingHelperTest extends UiServiceTestCase {
mUser, null, System.currentTimeMillis()), getLowChannel());
ArrayList<NotificationRecord> expected = new ArrayList<>();
+ expected.add(unrelated);
expected.add(summary);
expected.add(child2);
expected.add(child1);
- expected.add(unrelated);
ArrayList<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
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 9559a256d326..5fdb3965be76 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -6095,6 +6095,67 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(readPolicy.allowConversations()).isFalse();
}
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
+ ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
+ Policy previousManualPolicy = mZenModeHelper.mConfig.toNotificationPolicy();
+ ZenPolicy previousManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ previousManualPolicy);
+ ZenPolicy customZenPolicy = new ZenPolicy.Builder(defaultZenPolicy).allowConversations(
+ CONVERSATION_SENDERS_ANYONE).build();
+
+ mZenModeHelper.mConfig.automaticRules.clear();
+ addZenRule(mZenModeHelper.mConfig, "appWithDefault", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithSameAsManual", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithCustom", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, customZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithOtherFilter", "app.pkg",
+ ZEN_MODE_ALARMS, null);
+ addZenRule(mZenModeHelper.mConfig, "systemWithDefault", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "systemWithSameAsManual", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+
+ Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
+ mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+ ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
+
+ // Only app rules with default or same-as-manual policies were updated.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithDefault").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithSameAsManual").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithCustom").zenPolicy)
+ .isEqualTo(customZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithOtherFilter").zenPolicy)
+ .isNull();
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithDefault").zenPolicy)
+ .isEqualTo(defaultZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithSameAsManual").zenPolicy)
+ .isEqualTo(previousManualZenPolicy);
+ }
+
+ private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
+ @Nullable ZenPolicy zenPolicy) {
+ ZenRule rule = new ZenRule();
+ rule.id = id;
+ rule.pkg = ownerPkg;
+ rule.enabled = true;
+ rule.zenMode = zenMode;
+ rule.zenPolicy = zenPolicy;
+ // Plus stuff so that isValidAutomaticRule() passes
+ rule.name = String.format("Rule %s from %s with mode=%s and policy=%s", id, ownerPkg,
+ zenMode, zenPolicy);
+ rule.conditionId = Uri.parse(rule.name);
+
+ config.automaticRules.put(id, rule);
+ }
+
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
Parcel p = Parcel.obtain();
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 30eb5efc16a7..1da5001dab42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -116,6 +116,7 @@ import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
@@ -3507,6 +3508,23 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
+ public void testIsCameraActive() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final DisplayRotationCompatPolicy displayRotationCompatPolicy = mock(
+ DisplayRotationCompatPolicy.class);
+ when(mDisplayContent.getDisplayRotationCompatPolicy()).thenReturn(
+ displayRotationCompatPolicy);
+
+ when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+ anyBoolean())).thenReturn(false);
+ assertFalse(app.mActivityRecord.isCameraActive());
+
+ when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+ anyBoolean())).thenReturn(true);
+ assertTrue(app.mActivityRecord.isCameraActive());
+ }
+
+ @Test
public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
final ActivityRecord activity = createActivityWithTask();
// Mock a flag being enabled.
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 89ae802f474c..f92387cdf5c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,30 +21,24 @@ 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 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.DesktopModeLaunchParamsModifier.ENFORCE_DEVICE_RESTRICTIONS_KEY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
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_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import android.content.res.Resources;
import android.graphics.Rect;
-import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
-import com.android.internal.R;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
import com.android.window.flags.Flags;
@@ -52,7 +46,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
/**
* Tests for desktop mode task bounds.
@@ -80,6 +73,8 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
mCurrent.reset();
mResult = new LaunchParamsController.LaunchParams();
mResult.reset();
+
+ mTarget = new DesktopModeLaunchParamsModifier(mContext);
}
@Test
@@ -101,12 +96,12 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() {
+ public void testReturnsContinueIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() {
setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ true,
/*enforceDeviceRestrictions=*/ false);
final Task task = new TaskBuilder(mSupervisor).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@@ -139,22 +134,22 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
+ public void testReturnsContinueIfTaskUsingActivityTypeStandard() {
setupDesktopModeLaunchParamsModifier();
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
+ public void testReturnsContinueIfTaskUsingActivityTypeUndefined() {
setupDesktopModeLaunchParamsModifier();
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_UNDEFINED).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@@ -180,7 +175,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
task.getDisplayArea().setBounds(new Rect(0, 0, displayWidth, displayHeight));
final int desiredWidth = (int) (displayWidth * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight = (int) (displayHeight * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(desiredWidth, mResult.mBounds.width());
assertEquals(desiredHeight, mResult.mBounds.height());
}
@@ -196,7 +191,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
mCurrent.mPreferredTaskDisplayArea = mockTaskDisplayArea;
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(mockTaskDisplayArea, mResult.mPreferredTaskDisplayArea);
assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
}
@@ -208,15 +203,10 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
private void setupDesktopModeLaunchParamsModifier(boolean isDesktopModeSupported,
boolean enforceDeviceRestrictions) {
- Resources mockResources = Mockito.mock(Resources.class);
- when(mockResources.getBoolean(eq(R.bool.config_isDesktopModeSupported)))
- .thenReturn(isDesktopModeSupported);
- doReturn(mockResources).when(mContext).getResources();
-
- SystemProperties.set(ENFORCE_DEVICE_RESTRICTIONS_KEY,
- String.valueOf(enforceDeviceRestrictions));
-
- mTarget = new DesktopModeLaunchParamsModifier(mContext);
+ doReturn(isDesktopModeSupported)
+ .when(() -> DesktopModeLaunchParamsModifier.isDesktopModeSupported(any()));
+ doReturn(enforceDeviceRestrictions)
+ .when(DesktopModeLaunchParamsModifier::enforceDeviceRestrictions);
}
private class CalculateRequestBuilder {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 507140d573ea..262ba8bb32f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -38,6 +38,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -538,6 +539,42 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ public void testIsCameraActiveWhenCallbackInvokedNoMultiWindow_returnTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackNotInvokedNoMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackNotInvokedMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.inMultiWindowMode()).thenReturn(true);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackInvokedMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.inMultiWindowMode()).thenReturn(true);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
}
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 b41db3170ef6..b74da1a888cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -29,6 +29,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIE
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
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_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
@@ -73,6 +74,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
@@ -1086,6 +1089,39 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
}
+ @Test
+ public void testRecomputeConfigurationForCameraCompatIfNeeded() {
+ spyOn(mController);
+ doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(false).when(mController).shouldOverrideMinAspectRatioForCamera();
+ clearInvocations(mActivity);
+
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+
+ verify(mActivity, never()).recomputeConfiguration();
+
+ // isOverrideOrientationOnlyForCameraEnabled
+ doReturn(true).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+
+ // isCameraCompatSplitScreenAspectRatioAllowed
+ doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ doReturn(true).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+
+ // shouldOverrideMinAspectRatioForCamera
+ doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(true).when(mController).shouldOverrideMinAspectRatioForCamera();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+ }
+
private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
boolean orientationRequest) {
spyOn(mController);
@@ -1280,6 +1316,78 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
}
@Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
+ throws Exception {
+ doReturn(false).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.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);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.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);
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.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/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 64adff80f442..87f26e5a8eae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -200,6 +200,7 @@ public class SystemServicesTestRule implements TestRule {
.mockStatic(DisplayControl.class, mockStubOnly)
.mockStatic(LockGuard.class, mockStubOnly)
.mockStatic(Watchdog.class, mockStubOnly)
+ .spyStatic(DesktopModeLaunchParamsModifier.class)
.strictness(Strictness.LENIENT)
.startMocking();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a35a35acb6c1..9d14290bdd8a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1241,7 +1241,7 @@ public class UsageStatsService extends SystemService implements
break;
case Event.SHORTCUT_INVOCATION:
case Event.CHOOSER_ACTION:
- case Event.STANDBY_BUCKET_CHANGED:
+ // case Event.STANDBY_BUCKET_CHANGED:
case Event.FOREGROUND_SERVICE_START:
case Event.FOREGROUND_SERVICE_STOP:
logAppUsageEventReportedAtomLocked(event.mEventType, uid, event.mPackage);
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 461dfeca09e3..9a5e88becf1e 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.helpers
+import android.graphics.Rect
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
@@ -32,6 +33,14 @@ import androidx.test.uiautomator.Until
*/
open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
IStandardAppHelper by innerHelper {
+
+ enum class Corners {
+ LEFT_TOP,
+ RIGHT_TOP,
+ LEFT_BOTTOM,
+ RIGHT_BOTTOM
+ }
+
private val TIMEOUT_MS = 3_000L
private val CAPTION = "desktop_mode_caption"
private val CAPTION_HANDLE = "caption_handle"
@@ -121,4 +130,39 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
wmHelper.getWindowRegion(innerHelper).bounds.contains(it.visibleBounds)
}
}
+
+ /** Resize a desktop app from its corners. */
+ fun cornerResize(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ corner: Corners,
+ horizontalChange: Int,
+ verticalChange: Int
+ ) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val (startX, startY) = getStartCoordinatesForCornerResize(windowRect, corner)
+
+ // The position we want to drag to
+ val endY = startY + verticalChange
+ val endX = startX + horizontalChange
+
+ // drag the specified corner of the window to the end coordinate.
+ device.drag(startX, startY, endX, endY, 100)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ private fun getStartCoordinatesForCornerResize(
+ windowRect: Rect,
+ corner: Corners
+ ): Pair<Int, Int> {
+ return when (corner) {
+ Corners.LEFT_TOP -> Pair(windowRect.left, windowRect.top)
+ Corners.RIGHT_TOP -> Pair(windowRect.right, windowRect.top)
+ Corners.LEFT_BOTTOM -> Pair(windowRect.left, windowRect.bottom)
+ Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom)
+ }
+ }
}
diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
index f4845a518b20..11f46335f017 100644
--- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java
+++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
@@ -125,6 +125,14 @@ public class InputShellCommandTest {
assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
}
+ @Test
+ public void testInvalidKeyEventCommandArgsCombination() {
+ // --duration and --longpress must not be sent together
+ runCommand("keyevent --duration 1000 --longpress KEYCODE_A");
+
+ assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
+ }
+
private InputEvent getSingleInjectedInputEvent() {
assertThat(mInputEventInjector.mInjectedEvents).hasSize(1);
return mInputEventInjector.mInjectedEvents.get(0);
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c5a74d..f6885e1e74ba 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,13 +1,63 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
{ "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
- { "name": "hoststubgen-invoke-test" }
+ { "name": "hoststubgen-invoke-test" },
+ {
+ "name": "RavenwoodMockitoTest_device"
+ },
+ {
+ "name": "RavenwoodBivalentTest_device"
+ },
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
],
"ravenwood-presubmit": [
{
+ "name": "RavenwoodMinimumTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodMockitoTest",
+ "host": true
+ },
+ {
"name": "CtsUtilTestCasesRavenwood",
"host": true
+ },
+ {
+ "name": "RavenwoodCoreTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodBivalentTest",
+ "host": true
}
]
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 2f432cc7ac96..7212beb6ae4b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -16,6 +16,7 @@
package com.android.hoststubgen
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.dumper.ApiDumper
import com.android.hoststubgen.filters.AnnotationBasedFilter
import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
import com.android.hoststubgen.filters.ConstantFilter
@@ -89,7 +90,11 @@ class HostStubGen(val options: HostStubGenOptions) {
log.i("Dump file created at $it")
}
options.apiListFile.ifSet {
- PrintWriter(it).use { pw -> stats.dumpApis(pw) }
+ PrintWriter(it).use { pw ->
+ // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+ // framework-minus-apex.jar so that we can dump inherited methods from it.
+ ApiDumper(pw, allClasses, null, filter).dump()
+ }
log.i("API list file created at $it")
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index da6146911a21..9045db210495 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -15,7 +15,8 @@
*/
package com.android.hoststubgen
-import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
import org.objectweb.asm.Opcodes
import java.io.PrintWriter
@@ -55,8 +56,8 @@ open class HostStubGenStats {
// Ignore methods where policy isn't relevant
if (policy.isIgnoredForStats) return
- val packageName = resolvePackageName(fullClassName)
- val className = resolveOuterClassName(fullClassName)
+ val packageName = getPackageNameFromFullClassName(fullClassName)
+ val className = getOuterClassNameFromFullClassName(fullClassName)
// Ignore methods for certain generated code
if (className.endsWith("Proto")
@@ -88,42 +89,4 @@ open class HostStubGenStats {
}
}
}
-
- fun dumpApis(pw: PrintWriter) {
- pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
- apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
- .forEach { api ->
- pw.printf(
- "%s,%s,%s,%s\n",
- csvEscape(resolvePackageName(api.fullClassName)),
- csvEscape(resolveClassName(api.fullClassName)),
- csvEscape(api.methodName),
- csvEscape(api.methodDesc),
- )
- }
- }
-
- private fun resolvePackageName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- return fullClassName.substring(0, start).toHumanReadableClassName()
- }
-
- private fun resolveOuterClassName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- val end = fullClassName.indexOf('$')
- if (end == -1) {
- return fullClassName.substring(start + 1)
- } else {
- return fullClassName.substring(start + 1, end)
- }
- }
-
- private fun resolveClassName(fullClassName: String): String {
- val pos = fullClassName.lastIndexOf('/')
- if (pos == -1) {
- return fullClassName
- } else {
- return fullClassName.substring(pos + 1)
- }
- }
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 83e122feeeb2..b8d18001f37b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -76,17 +76,45 @@ fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): Strin
return null
}
-private val removeLastElement = """[./][^./]*$""".toRegex()
+val periodOrSlash = charArrayOf('.', '/')
+
+fun getPackageNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return ""
+ } else {
+ return fullClassName.substring(0, pos)
+ }
+}
+
+fun getClassNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return fullClassName
+ } else {
+ return fullClassName.substring(pos + 1)
+ }
+}
-fun getPackageNameFromClassName(className: String): String {
- return className.replace(removeLastElement, "")
+fun getOuterClassNameFromFullClassName(fullClassName: String): String {
+ val start = fullClassName.lastIndexOfAny(periodOrSlash)
+ val end = fullClassName.indexOf('$')
+ if (end == -1) {
+ return fullClassName.substring(start + 1)
+ } else {
+ return fullClassName.substring(start + 1, end)
+ }
}
-fun resolveClassName(className: String, packageName: String): String {
+/**
+ * If [className] is a fully qualified name, just return it.
+ * Otherwise, prepend [defaultPackageName].
+ */
+fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
if (className.contains('.') || className.contains('/')) {
return className
}
- return "$packageName.$className"
+ return "$defaultPackageName.$className"
}
fun String.toJvmClassName(): String {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
new file mode 100644
index 000000000000..aaefee4f71e8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.hoststubgen.dumper
+
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.csvEscape
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.log
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import java.io.PrintWriter
+
+/**
+ * Dump all the API methods in [classes], with inherited methods, with their policies.
+ */
+class ApiDumper(
+ val pw: PrintWriter,
+ val classes: ClassNodes,
+ val frameworkClasses: ClassNodes?,
+ val filter: OutputFilter,
+) {
+ private data class MethodKey(
+ val name: String,
+ val descriptor: String,
+ )
+
+ val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")
+
+ private val shownMethods = mutableSetOf<MethodKey>()
+
+ /**
+ * Do the dump.
+ */
+ fun dump() {
+ pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
+ ",Supported,Policy,Reason\n")
+
+ classes.forEach { classNode ->
+ shownMethods.clear()
+ dump(classNode, classNode)
+ }
+ }
+
+ private fun dumpMethod(
+ classPackage: String,
+ className: String,
+ isSuperClass: Boolean,
+ methodClassName: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ pw.printf(
+ "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
+ csvEscape(classPackage),
+ csvEscape(className),
+ if (isSuperClass) { 1 } else { 0 },
+ csvEscape(methodClassName),
+ csvEscape(methodName),
+ csvEscape(methodDesc),
+ if (policy.policy.isSupported) { 1 } else { 0 },
+ policy.policy,
+ csvEscape(policy.reason),
+ )
+ }
+
+ private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
+ val methodKey = MethodKey(methodName, methodDesc)
+
+ if (shownMethods.contains(methodKey)) {
+ return true
+ }
+ shownMethods.add(methodKey)
+ return false
+ }
+
+ private fun dump(
+ dumpClass: ClassNode,
+ methodClass: ClassNode,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val isSuperClass = dumpClass != methodClass
+
+ methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method ->
+
+ // Don't print ctor's from super classes.
+ if (isSuperClass) {
+ if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) {
+ return@forEach
+ }
+ }
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(method.name, method.desc)) {
+ return@forEach
+ }
+
+ val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
+
+ // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
+ // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
+ // and for now we don't have an easy way to detect it.
+ if (policy.policy == FilterPolicy.Remove) {
+ return@forEach
+ }
+
+ val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
+
+ dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
+ renameTo ?: method.name, method.desc, policy)
+ }
+
+ // Dump super class methods.
+ dumpSuper(dumpClass, methodClass.superName)
+
+ // Dump interface methods (which may have default methods).
+ methodClass.interfaces?.sorted()?.forEach { interfaceName ->
+ dumpSuper(dumpClass, interfaceName)
+ }
+ }
+
+ /**
+ * Dump a given super class / interface.
+ */
+ private fun dumpSuper(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ classes.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ frameworkClasses?.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ if (methodClassName.startsWith("java/") ||
+ methodClassName.startsWith("javax/")
+ ) {
+ dumpStandardClass(dumpClass, methodClassName)
+ return
+ }
+ log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+
+ /**
+ * Dump methods from Java standard classes.
+ */
+ private fun dumpStandardClass(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val methodClassName = methodClassName.toHumanReadableClassName()
+
+ try {
+ val clazz = Class.forName(methodClassName)
+
+ // Method.getMethods() returns only public methods, but with inherited ones.
+ // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
+ //
+ // Since we're only interested in public ones, just use getMethods().
+ clazz.methods.forEach { method ->
+ val methodName = method.name
+ val methodDesc = Type.getMethodDescriptor(method)
+
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(methodName, methodDesc)) {
+ return@forEach
+ }
+
+ dumpMethod(pkg, cls, true, methodClassName,
+ methodName, methodDesc, javaStandardApiPolicy)
+ }
+ } catch (e: ClassNotFoundException) {
+ log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 45e140c8e3ff..6643492a1394 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -20,8 +20,8 @@ import com.android.hoststubgen.HostStubGenStats
import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
-import com.android.hoststubgen.asm.getPackageNameFromClassName
-import com.android.hoststubgen.asm.resolveClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
@@ -89,7 +89,7 @@ abstract class BaseAdapter (
) {
super.visit(version, access, name, signature, superName, interfaces)
currentClassName = name
- currentPackageName = getPackageNameFromClassName(name)
+ currentPackageName = getPackageNameFromFullClassName(name)
classPolicy = filter.getPolicyForClass(currentClassName)
log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
@@ -98,7 +98,8 @@ abstract class BaseAdapter (
log.indent()
filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
- val fullClassName = resolveClassName(className, currentPackageName).toJvmClassName()
+ val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName)
+ .toJvmClassName()
log.d(" NativeSubstitutionClass: $fullClassName")
if (classes.findClass(fullClassName) == null) {
log.w("Native substitution class $fullClassName not found. Class must be " +