summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcmds/am/am.sh5
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java17
-rw-r--r--cmds/uinput/tests/Android.bp14
-rw-r--r--core/java/android/app/Notification.java8
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java74
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl2
-rw-r--r--core/java/android/content/pm/ActivityInfo.java13
-rw-r--r--core/java/android/content/res/flags.aconfig13
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java18
-rw-r--r--core/java/android/os/TestLooperManager.java2
-rw-r--r--core/java/android/provider/Settings.java18
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java20
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java28
-rw-r--r--core/java/android/window/DesktopModeFlags.java1
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.java43
-rw-r--r--core/java/android/window/WindowTokenClient.java1
-rw-r--r--core/java/android/window/WindowTokenClientController.java17
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig12
-rw-r--r--core/java/com/android/internal/app/IAppOpsCallback.aidl23
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java77
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java24
-rw-r--r--core/java/com/android/internal/protolog/WmProtoLogGroups.java2
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java107
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/values/config.xml15
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java48
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java6
-rw-r--r--core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java21
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java568
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java4
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt4
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt11
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt3
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml12
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml19
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt80
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt54
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt192
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt104
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt11
-rw-r--r--media/java/android/media/audiofx/HapticGenerator.java14
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_english_india.kcm400
-rw-r--r--packages/InputDevices/res/values/strings.xml3
-rw-r--r--packages/InputDevices/res/xml/keyboard_layouts.xml7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt64
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java37
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java4
-rw-r--r--packages/Shell/src/com/android/shell/BugreportPrefs.java29
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java8
-rw-r--r--packages/Shell/src/com/android/shell/BugreportWarningActivity.java23
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java37
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt41
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt222
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt135
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt115
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt328
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt75
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt99
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java7
-rw-r--r--packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml20
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml8
-rw-r--r--packages/SystemUI/res/layout/combined_qs_header.xml1
-rw-r--r--packages/SystemUI/res/layout/ongoing_activity_chip_content.xml7
-rw-r--r--packages/SystemUI/res/layout/system_icons.xml7
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_button.xml25
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/values/styles.xml9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java23
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java40
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java75
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java4
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java26
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/SecureChannel.java13
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java32
-rw-r--r--services/companion/java/com/android/server/companion/transport/SecureTransport.java15
-rw-r--r--services/companion/java/com/android/server/companion/transport/TransportUtils.java77
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java19
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java26
-rw-r--r--services/core/java/com/android/server/app/GameManagerSettings.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java24
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java10
-rw-r--r--services/core/java/com/android/server/display/mode/VotesStatsReporter.java38
-rw-r--r--services/core/java/com/android/server/flags/services.aconfig7
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java75
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java15
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java3
-rw-r--r--services/core/java/com/android/server/notification/NotificationChannelExtractor.java2
-rw-r--r--services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java110
-rw-r--r--services/core/java/com/android/server/security/CertificateRevocationStatusManager.java366
-rw-r--r--services/core/java/com/android/server/security/OWNERS1
-rw-r--r--services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java81
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java150
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/AnimatingActivityRegistry.java120
-rw-r--r--services/core/java/com/android/server/wm/AnimationAdapter.java9
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java2
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java1352
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java48
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java39
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java15
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java17
-rw-r--r--services/core/java/com/android/server/wm/PresentationController.java86
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java64
-rw-r--r--services/core/java/com/android/server/wm/Session.java10
-rw-r--r--services/core/java/com/android/server/wm/SnapshotController.java5
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java23
-rw-r--r--services/core/java/com/android/server/wm/Task.java29
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java17
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java28
-rw-r--r--services/credentials/java/com/android/server/credentials/RequestSession.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java12
-rw-r--r--services/incremental/IncrementalService.cpp4
-rw-r--r--services/incremental/IncrementalService.h7
-rw-r--r--services/incremental/ServiceWrappers.h3
-rw-r--r--services/incremental/test/IncrementalServiceTest.cpp2
-rw-r--r--services/java/com/android/server/SystemServer.java32
-rw-r--r--services/java/com/android/server/flags.aconfig7
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java242
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java112
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java131
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java128
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java107
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java1306
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java520
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java76
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java78
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java13
-rw-r--r--tests/AttestationVerificationTest/AndroidManifest.xml2
-rw-r--r--tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json12
-rw-r--r--tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json16
-rw-r--r--tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java303
-rw-r--r--tests/FlickerTests/IME/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/Rotation/AndroidTestTemplate.xml2
266 files changed, 6151 insertions, 5864 deletions
diff --git a/cmds/am/am.sh b/cmds/am/am.sh
index 76ec214cb446..f099be3e26a2 100755
--- a/cmds/am/am.sh
+++ b/cmds/am/am.sh
@@ -1,11 +1,10 @@
#!/system/bin/sh
-# set to top-app process group
-settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
-
if [ "$1" != "instrument" ] ; then
cmd activity "$@"
else
+ # set to top-app process group for instrument
+ settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6310d32515c5..696bc82a9ffc 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,6 +18,7 @@ package com.android.commands.bmgr;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupProgress;
@@ -73,6 +74,8 @@ public class Bmgr {
"Error: Could not access the backup transport. Is the system running?";
private static final String PM_NOT_RUNNING_ERR =
"Error: Could not access the Package Manager. Is the system running?";
+ private static final String INVALID_USER_ID_ERR_TEMPLATE =
+ "Error: Invalid user id (%d).\n";
private String[] mArgs;
private int mNextArg;
@@ -104,6 +107,11 @@ public class Bmgr {
mArgs = args;
mNextArg = 0;
int userId = parseUserId();
+ if (userId < 0) {
+ System.err.printf(INVALID_USER_ID_ERR_TEMPLATE, userId);
+ return;
+ }
+
String op = nextArg();
Slog.v(TAG, "Running " + op + " for user:" + userId);
@@ -955,12 +963,15 @@ public class Bmgr {
private int parseUserId() {
String arg = nextArg();
- if ("--user".equals(arg)) {
- return UserHandle.parseUserArg(nextArg());
- } else {
+ if (!"--user".equals(arg)) {
mNextArg--;
return UserHandle.USER_SYSTEM;
}
+ int userId = UserHandle.parseUserArg(nextArg());
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+ return userId;
}
private static void showUsage() {
diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp
index e728bd270a46..516de3325f77 100644
--- a/cmds/uinput/tests/Android.bp
+++ b/cmds/uinput/tests/Android.bp
@@ -18,3 +18,17 @@ android_test {
"device-tests",
],
}
+
+android_ravenwood_test {
+ name: "UinputTestsRavenwood",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "frameworks-base-testutils",
+ "platform-test-annotations",
+ "truth",
+ "uinput",
+ ],
+}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dce15b833bbb..3633b4eb333a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11370,7 +11370,7 @@ public class Notification implements Parcelable
if (mProgressPoints == null) {
mProgressPoints = new ArrayList<>();
}
- if (point.getPosition() >= 0) {
+ if (point.getPosition() > 0) {
mProgressPoints.add(point);
if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) {
@@ -11379,7 +11379,7 @@ public class Notification implements Parcelable
}
} else {
- Log.w(TAG, "Dropped the point. The position is a negative integer.");
+ Log.w(TAG, "Dropped the point. The position is a negative or zero integer.");
}
return this;
@@ -11893,7 +11893,9 @@ public class Notification implements Parcelable
final List<Point> points = new ArrayList<>();
for (Point point : mProgressPoints) {
final int position = point.getPosition();
- if (position < 0 || position > totalLength) continue;
+ // The points at start/end aren't supposed to show in the progress bar.
+ // Therefore those are also dropped here.
+ if (position <= 0 || position >= totalLength) continue;
points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
if (points.size() == MAX_PROGRESS_POINT_LIMIT) {
break;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 566e78a8de35..2b0e941cf602 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -277,6 +277,23 @@ public final class CompanionDeviceManager {
*/
public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TRANSPORT_FLAG_" }, value = {
+ TRANSPORT_FLAG_EXTEND_PATCH_DIFF,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransportFlags {}
+
+ /**
+ * A security flag that allows transports to be attached to devices that may be more vulnerable
+ * due to infrequent updates. Can only be used for associations with
+ * {@link AssociationRequest#DEVICE_PROFILE_WEARABLE_SENSING} device profile.
+ *
+ * @hide
+ */
+ public static final int TRANSPORT_FLAG_EXTEND_PATCH_DIFF = 1;
+
/**
* Callback for applications to receive updates about and the outcome of
* {@link AssociationRequest} issued via {@code associate()} call.
@@ -1452,7 +1469,52 @@ public final class CompanionDeviceManager {
}
try {
- final Transport transport = new Transport(associationId, in, out);
+ final Transport transport = new Transport(associationId, in, out, 0);
+ mTransports.put(associationId, transport);
+ transport.start();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to attach transport", e);
+ }
+ }
+ }
+
+ /**
+ * Attach a bidirectional communication stream to be used as a transport channel for
+ * transporting system data between associated devices. Flags can be provided to further
+ * customize the behavior of the transport.
+ *
+ * @param associationId id of the associated device.
+ * @param in Already connected stream of data incoming from remote
+ * associated device.
+ * @param out Already connected stream of data outgoing to remote associated
+ * device.
+ * @param flags Flags to customize transport behavior.
+ * @throws DeviceNotAssociatedException Thrown if the associationId was not previously
+ * associated with this app.
+ *
+ * @see #buildPermissionTransferUserConsentIntent(int)
+ * @see #startSystemDataTransfer(int, Executor, OutcomeReceiver)
+ * @see #detachSystemDataTransport(int)
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
+ public void attachSystemDataTransport(int associationId,
+ @NonNull InputStream in,
+ @NonNull OutputStream out,
+ @TransportFlags int flags) throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
+ synchronized (mTransports) {
+ if (mTransports.contains(associationId)) {
+ detachSystemDataTransport(associationId);
+ }
+
+ try {
+ final Transport transport = new Transport(associationId, in, out, flags);
mTransports.put(associationId, transport);
transport.start();
} catch (IOException e) {
@@ -1931,16 +1993,22 @@ public final class CompanionDeviceManager {
private final int mAssociationId;
private final InputStream mRemoteIn;
private final OutputStream mRemoteOut;
+ private final int mFlags;
private InputStream mLocalIn;
private OutputStream mLocalOut;
private volatile boolean mStopped;
- public Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+ Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+ this(associationId, remoteIn, remoteOut, 0);
+ }
+
+ Transport(int associationId, InputStream remoteIn, OutputStream remoteOut, int flags) {
mAssociationId = associationId;
mRemoteIn = remoteIn;
mRemoteOut = remoteOut;
+ mFlags = flags;
}
public void start() throws IOException {
@@ -1957,7 +2025,7 @@ public final class CompanionDeviceManager {
try {
mService.attachSystemDataTransport(mContext.getOpPackageName(),
- mContext.getUserId(), mAssociationId, remoteFd);
+ mContext.getUserId(), mAssociationId, remoteFd, mFlags);
} catch (RemoteException e) {
throw new IOException("Failed to configure transport", e);
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index a2b7dd9c3d0e..787e8b65a736 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -113,7 +113,7 @@ interface ICompanionDeviceManager {
in ISystemDataTransferCallback callback);
@EnforcePermission("DELIVER_COMPANION_MESSAGES")
- void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
+ void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd, int flags);
@EnforcePermission("DELIVER_COMPANION_MESSAGES")
void detachSystemDataTransport(String packageName, int userId, int associationId);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 37f3f17ebe42..e6450606d450 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1643,6 +1643,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public static final long OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION = 327313645L;
/**
+ * When the override is enabled, the activity receives configuration coupled with caption bar
+ * insets. Normally, caption bar insets are decoupled from configuration.
+ *
+ * <p>Override applies only if the activity targets SDK level 34 or earlier version.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS = 388014743L;
+
+ /**
* Optional set of a certificates identifying apps that are allowed to embed this activity. From
* the "knownActivityEmbeddingCerts" attribute.
*/
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 027eb9df1d9e..88fbdaddb3d2 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -137,4 +137,15 @@ flag {
namespace: "resource_manager"
description: "flag always meant to be false, for testing resource flagging within cts tests"
bug: "377974898"
-} \ No newline at end of file
+}
+
+flag {
+ name: "use_new_aconfig_storage"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Retrieve flag values from new Aconfig flag storage in AconfigFlags.java"
+ bug: "352348353"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index fded88212127..d8919160320a 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -641,6 +641,9 @@ public final class DisplayManager {
* is triggered whenever the properties of a {@link android.view.Display}, such as size,
* state, density are modified.
*
+ * This event is not triggered for refresh rate changes as they can change very often.
+ * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
+ *
* @see #registerDisplayListener(DisplayListener, Handler, long)
*
*/
@@ -839,6 +842,9 @@ public final class DisplayManager {
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
+ * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
+ * instead to subscribe for explicit events of interest
+ *
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
@@ -847,7 +853,9 @@ public final class DisplayManager {
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
- | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_CHANGED
+ | EVENT_TYPE_DISPLAY_REFRESH_RATE
+ | EVENT_TYPE_DISPLAY_REMOVED);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b5715ed25bd9..339dbf2c2029 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,29 +1766,23 @@ public final class DisplayManagerGlobal {
}
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
- // For backward compatibility, a client subscribing to
- // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
- // RR changes
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
}
- if ((eventFlags
- & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
- if (Flags.displayListenerPerformanceImprovements()) {
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
- }
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
+ if (Flags.displayListenerPerformanceImprovements()) {
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
}
}
-
return baseEventMask;
}
}
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 2d9d025b8d80..1a54f4df58fb 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -159,7 +159,7 @@ public class TestLooperManager {
*/
public void execute(Message message) {
checkReleased();
- if (Looper.myLooper() == mLooper) {
+ if (mLooper.isCurrentThread()) {
// This is being called from the thread it should be executed on, we can just dispatch.
message.target.dispatchMessage(message);
} else {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3cd7a00591ca..f1a9514107da 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13006,6 +13006,24 @@ public final class Settings {
public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled";
/**
+ * Toggle for whether to redact OTP notification while connected to wifi. Defaults to
+ * false/0.
+ * @hide
+ */
+ @Readable
+ public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI =
+ "redact_otp_on_wifi";
+
+ /**
+ * Toggle for whether to immediately redact OTP notifications, or require the device to be
+ * locked for 10 minutes. Defaults to false/0
+ * @hide
+ */
+ @Readable
+ public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY =
+ "remove_otp_redaction_delay";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 195896dc8edf..0e78bfdb5069 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -180,7 +180,7 @@ public final class InputEventConsistencyVerifier {
final MotionEvent motionEvent = (MotionEvent)event;
if (motionEvent.isTouchEvent()) {
onTouchEvent(motionEvent, nestingLevel);
- } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ } else if (motionEvent.isFromSource(InputDevice.SOURCE_TRACKBALL)) {
onTrackballEvent(motionEvent, nestingLevel);
} else {
onGenericMotionEvent(motionEvent, nestingLevel);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0d6f82773622..80b4f2caabbb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -272,9 +272,9 @@ import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.ScreenCapture;
import android.window.SurfaceSyncGroup;
-import android.window.WindowContext;
import android.window.WindowOnBackInvokedDispatcher;
import android.window.WindowTokenClient;
+import android.window.WindowTokenClientController;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -6614,12 +6614,15 @@ public final class ViewRootImpl implements ViewParent,
} else {
if (enableWindowContextResourcesUpdateOnConfigChange()) {
// There is no activity callback - update resources for window token, if needed.
- final WindowTokenClient windowTokenClient = getWindowTokenClient();
- if (windowTokenClient != null) {
- windowTokenClient.onConfigurationChanged(
+ final IBinder windowContextToken = mContext.getWindowContextToken();
+ if (windowContextToken instanceof WindowTokenClient) {
+ WindowTokenClientController.getInstance().onWindowConfigurationChanged(
+ windowContextToken,
mLastReportedMergedConfiguration.getMergedConfiguration(),
- newDisplayId == INVALID_DISPLAY ? mDisplay.getDisplayId()
- : newDisplayId);
+ newDisplayId == INVALID_DISPLAY
+ ? mDisplay.getDisplayId()
+ : newDisplayId
+ );
}
}
updateConfiguration(newDisplayId);
@@ -6627,11 +6630,6 @@ public final class ViewRootImpl implements ViewParent,
mForceNextConfigUpdate = false;
}
- private WindowTokenClient getWindowTokenClient() {
- if (!(mContext instanceof WindowContext)) return null;
- return (WindowTokenClient) mContext.getWindowContextToken();
- }
-
/**
* Update display and views if last applied merged configuration changed.
* @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0fb80422833c..56f0415b40cc 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3778,8 +3778,32 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
if (Flags.refactorInsetsController()) {
- mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
- false /* fromIme */, statsToken);
+ synchronized (mH) {
+ Handler vh = rootView.getHandler();
+ if (vh == null) {
+ // If the view doesn't have a handler, something has changed out from
+ // under us.
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+ return;
+ }
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+
+ if (vh.getLooper() != Looper.myLooper()) {
+ // The view is running on a different thread than our own, so
+ // we need to reschedule our work for over there.
+ if (DEBUG) {
+ Log.v(TAG, "Close current input: reschedule hide to view thread");
+ }
+ final var viewRootImpl = mCurRootView;
+ vh.post(() -> viewRootImpl.getInsetsController().hide(
+ WindowInsets.Type.ime(), false /* fromIme */, statsToken));
+ } else {
+ mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
+ false /* fromIme */, statsToken);
+ }
+ }
} else {
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 785246074cee..1ce5df7cd137 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -55,6 +55,7 @@ public enum DesktopModeFlags {
Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
true),
+ ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 89327fe358f5..bc5ad50483ee 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.graphics.Rect;
import android.os.IBinder;
@@ -112,12 +113,21 @@ public final class TaskFragmentCreationParams implements Parcelable {
*/
private final @ScreenOrientation int mOverrideOrientation;
+ /**
+ * {@link android.content.pm.ActivityInfo.Config} mask that specifies which
+ * configuration changes should trigger TaskFragment info change callbacks.
+ *
+ * @see android.content.pm.ActivityInfo.Config
+ */
+ private final @ActivityInfo.Config int mConfigurationChangeMask;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
@Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
- @ScreenOrientation int overrideOrientation) {
+ @ScreenOrientation int overrideOrientation,
+ @ActivityInfo.Config int configurationChangeMask) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -131,6 +141,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mPairedActivityToken = pairedActivityToken;
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
mOverrideOrientation = overrideOrientation;
+ mConfigurationChangeMask = configurationChangeMask;
}
@NonNull
@@ -186,6 +197,11 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mOverrideOrientation;
}
+ /** @hide */
+ public @ActivityInfo.Config int getConfigurationChangeMask() {
+ return mConfigurationChangeMask;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -196,6 +212,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mPairedActivityToken = in.readStrongBinder();
mAllowTransitionWhenEmpty = in.readBoolean();
mOverrideOrientation = in.readInt();
+ mConfigurationChangeMask = in.readInt();
}
/** @hide */
@@ -210,6 +227,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
dest.writeStrongBinder(mPairedActivityToken);
dest.writeBoolean(mAllowTransitionWhenEmpty);
dest.writeInt(mOverrideOrientation);
+ dest.writeInt(mConfigurationChangeMask);
}
@NonNull
@@ -238,6 +256,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " pairedActivityToken=" + mPairedActivityToken
+ " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ " overrideOrientation=" + mOverrideOrientation
+ + " configurationChangeMask=" + mConfigurationChangeMask
+ "}";
}
@@ -275,6 +294,8 @@ public final class TaskFragmentCreationParams implements Parcelable {
private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ private @ActivityInfo.Config int mConfigurationChangeMask = 0;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -369,12 +390,30 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this;
}
+ /**
+ * Sets {@link android.content.pm.ActivityInfo.Config} mask that specifies which
+ * configuration changes should trigger TaskFragment info change callbacks.
+ *
+ * Only system organizers are allowed to configure this value. This value is ignored for
+ * non-system organizers.
+ *
+ * @see android.content.pm.ActivityInfo.Config
+ * @hide
+ */
+ @NonNull
+ public Builder setConfigurationChangeMask(
+ @ActivityInfo.Config int configurationChangeMask) {
+ mConfigurationChangeMask = configurationChangeMask;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
+ mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation,
+ mConfigurationChangeMask);
}
}
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index f7bee619bc4b..5a544d3549a0 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -107,6 +107,7 @@ public class WindowTokenClient extends Binder {
* @param newDisplayId the updated {@link android.view.Display} ID
*/
@MainThread
+ @VisibleForTesting(visibility = PACKAGE)
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
}
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index fcd7dfbb1769..72278d927d74 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -25,7 +25,9 @@ import android.app.IApplicationThread;
import android.app.servertransaction.WindowContextInfoChangeItem;
import android.app.servertransaction.WindowContextWindowRemovalItem;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -50,6 +52,7 @@ public class WindowTokenClientController {
private final Object mLock = new Object();
private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
.getApplicationThread();
+ private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
/** Attached {@link WindowTokenClient}. */
@GuardedBy("mLock")
@@ -257,6 +260,20 @@ public class WindowTokenClientController {
}
}
+ /** Propagates the configuration change to the client token. */
+ public void onWindowConfigurationChanged(@NonNull IBinder clientToken,
+ @NonNull Configuration config, int displayId) {
+ final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
+ if (windowTokenClient != null) {
+ // Let's make sure it's called on the main thread!
+ if (mHandler.getLooper().isCurrentThread()) {
+ windowTokenClient.onConfigurationChanged(config, displayId);
+ } else {
+ windowTokenClient.postOnConfigurationChanged(config, displayId);
+ }
+ }
+ }
+
@Nullable
private WindowTokenClient getWindowTokenClientIfAttached(@NonNull IBinder clientToken) {
if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 509e084e01e6..b805ac560b8d 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -695,4 +695,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "enable_desktop_close_shortcut_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Fix the window-close keyboard shortcut in Desktop Mode."
+ bug: "394599430"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
deleted file mode 100644
index 3a9525c03161..000000000000
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.app;
-
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName, String persistentDeviceId);
-}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 445dac7411da..21d000dc5224 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -16,6 +16,7 @@
package com.android.internal.pm.pkg.component;
+import static android.provider.flags.Flags.newStoragePublicApi;
import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
import android.aconfig.DeviceProtos;
@@ -27,6 +28,7 @@ import android.annotation.Nullable;
import android.content.res.Flags;
import android.os.Environment;
import android.os.Process;
+import android.os.flagging.AconfigPackage;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.Xml;
@@ -43,6 +45,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* A class that manages a cache of all device feature flags and their default + override values.
@@ -58,7 +61,8 @@ public class AconfigFlags {
private static final String OVERRIDE_PREFIX = "device_config_overrides/";
private static final String STAGED_PREFIX = "staged/";
- private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+ private final Map<String, Boolean> mFlagValues = new ArrayMap<>();
+ private final Map<String, AconfigPackage> mAconfigPackages = new ConcurrentHashMap<>();
public AconfigFlags() {
if (!Flags.manifestFlagging()) {
@@ -67,23 +71,33 @@ public class AconfigFlags {
}
return;
}
- final var defaultFlagProtoFiles =
- (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
- : Arrays.asList(DeviceProtos.PATHS);
- for (String fileName : defaultFlagProtoFiles) {
- try (var inputStream = new FileInputStream(fileName)) {
- loadAconfigDefaultValues(inputStream.readAllBytes());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+
+ if (useNewStorage()) {
+ Slog.i(LOG_TAG, "Using new flag storage");
+ } else {
+ Slog.i(LOG_TAG, "Using OLD proto flag storage");
+ final var defaultFlagProtoFiles =
+ (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
+ : Arrays.asList(DeviceProtos.PATHS);
+ for (String fileName : defaultFlagProtoFiles) {
+ try (var inputStream = new FileInputStream(fileName)) {
+ loadAconfigDefaultValues(inputStream.readAllBytes());
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+ }
+ }
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // Server overrides are only accessible to the system, no need to even try loading
+ // them in user processes.
+ loadServerOverrides();
}
- }
- if (Process.myUid() == Process.SYSTEM_UID) {
- // Server overrides are only accessible to the system, no need to even try loading them
- // in user processes.
- loadServerOverrides();
}
}
+ private static boolean useNewStorage() {
+ return newStoragePublicApi() && Flags.useNewAconfigStorage();
+ }
+
private void loadServerOverrides() {
// Reading the proto files is enough for READ_ONLY flags but if it's a READ_WRITE flag
// (which you can check with `flag.getPermission() == flag_permission.READ_WRITE`) then we
@@ -200,7 +214,40 @@ public class AconfigFlags {
*/
@Nullable
public Boolean getFlagValue(@NonNull String flagPackageAndName) {
- Boolean value = mFlagValues.get(flagPackageAndName);
+ if (useNewStorage()) {
+ return getFlagValueFromNewStorage(flagPackageAndName);
+ } else {
+ Boolean value = mFlagValues.get(flagPackageAndName);
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+ }
+ return value;
+ }
+ }
+
+ private Boolean getFlagValueFromNewStorage(String flagPackageAndName) {
+ int index = flagPackageAndName.lastIndexOf('.');
+ if (index < 0) {
+ Slog.e(LOG_TAG, "Unable to parse package name from " + flagPackageAndName);
+ return null;
+ }
+ String flagPackage = flagPackageAndName.substring(0, index);
+ String flagName = flagPackageAndName.substring(index + 1);
+ Boolean value = null;
+ AconfigPackage aconfigPackage = mAconfigPackages.computeIfAbsent(flagPackage, p -> {
+ try {
+ return AconfigPackage.load(p);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Failed to load aconfig package " + p, e);
+ return null;
+ }
+ });
+ if (aconfigPackage != null) {
+ // Default value is false for when the flag is not found.
+ // Note: Unlike with the old storage, with AconfigPackage, we don't have a way to
+ // know if the flag is not found or if it's found but the value is false.
+ value = aconfigPackage.getBooleanFlagValue(flagName, false);
+ }
if (DEBUG) {
Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 05a33fe830e8..d8cf258e23ba 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -160,19 +160,21 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
Objects.requireNonNull(mConfigurationService,
"A null ProtoLog Configuration Service was provided!");
- try {
- var args = createConfigurationServiceRegisterClientArgs();
+ mBackgroundLoggingService.execute(() -> {
+ try {
+ var args = createConfigurationServiceRegisterClientArgs();
- final var groupArgs = mLogGroups.values().stream()
- .map(group -> new RegisterClientArgs
- .GroupConfig(group.name(), group.isLogToLogcat()))
- .toArray(RegisterClientArgs.GroupConfig[]::new);
- args.setGroups(groupArgs);
+ final var groupArgs = mLogGroups.values().stream()
+ .map(group -> new RegisterClientArgs
+ .GroupConfig(group.name(), group.isLogToLogcat()))
+ .toArray(RegisterClientArgs.GroupConfig[]::new);
+ args.setGroups(groupArgs);
- mConfigurationService.registerClient(this, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed to register ProtoLog client");
- }
+ mConfigurationService.registerClient(this, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to register ProtoLog client");
+ }
+ });
}
/**
diff --git a/core/java/com/android/internal/protolog/WmProtoLogGroups.java b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
index 4bd5d24b71e2..5edc2fbd4c8f 100644
--- a/core/java/com/android/internal/protolog/WmProtoLogGroups.java
+++ b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
@@ -100,6 +100,8 @@ public enum WmProtoLogGroups implements IProtoLogGroup {
WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
WM_DEBUG_EMBEDDED_WINDOWS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_PRESENTATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 5e82772730b7..905d4dd547f3 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -83,7 +83,7 @@ public final class NotificationProgressBar extends ProgressBar implements
/** @see R.styleable#NotificationProgressBar_trackerHeight */
private final int mTrackerHeight;
- private int mTrackerWidth;
+ private int mTrackerDrawWidth = 0;
private int mTrackerPos;
private final Matrix mMatrix = new Matrix();
private Matrix mTrackerDrawMatrix = null;
@@ -157,7 +157,7 @@ public final class NotificationProgressBar extends ProgressBar implements
} else {
// TODO: b/372908709 - maybe don't rerun the entire calculation every time the
// progress model is updated? For example, if the segments and parts aren't changed,
- // there is no need to call `processAndConvertToViewParts` again.
+ // there is no need to call `processModelAndConvertToViewParts` again.
final int progress = mProgressModel.getProgress();
final int progressMax = mProgressModel.getProgressMax();
@@ -286,8 +286,11 @@ public final class NotificationProgressBar extends ProgressBar implements
private void configureTrackerBounds() {
// Reset the tracker draw matrix to null
mTrackerDrawMatrix = null;
+ mTrackerDrawWidth = 0;
- if (mTracker == null || mTrackerHeight <= 0) {
+ if (mTracker == null) return;
+ if (mTrackerHeight <= 0) {
+ mTrackerDrawWidth = mTracker.getIntrinsicWidth();
return;
}
@@ -306,14 +309,14 @@ public final class NotificationProgressBar extends ProgressBar implements
if (dWidth > maxDWidth) {
scale = (float) mTrackerHeight / (float) dHeight;
dx = (maxDWidth * scale - dWidth * scale) * 0.5f;
- mTrackerWidth = (int) (maxDWidth * scale);
+ mTrackerDrawWidth = (int) (maxDWidth * scale);
} else if (dHeight > maxDHeight) {
scale = (float) mTrackerHeight * 0.5f / (float) dWidth;
dy = (maxDHeight * scale - dHeight * scale) * 0.5f;
- mTrackerWidth = mTrackerHeight / 2;
+ mTrackerDrawWidth = mTrackerHeight / 2;
} else {
scale = (float) mTrackerHeight / (float) dHeight;
- mTrackerWidth = (int) (dWidth * scale);
+ mTrackerDrawWidth = (int) (dWidth * scale);
}
mTrackerDrawMatrix.setScale(scale, scale);
@@ -449,7 +452,8 @@ public final class NotificationProgressBar extends ProgressBar implements
segSegGap,
segPointGap,
pointRadius,
- mHasTrackerIcon
+ mHasTrackerIcon,
+ mTrackerDrawWidth
);
final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
@@ -465,7 +469,6 @@ public final class NotificationProgressBar extends ProgressBar implements
segmentMinWidth,
pointRadius,
progressFraction,
- width,
isStyledByProgress,
progressGap
);
@@ -493,8 +496,8 @@ public final class NotificationProgressBar extends ProgressBar implements
pointRadius,
mHasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
ex);
@@ -522,8 +525,8 @@ public final class NotificationProgressBar extends ProgressBar implements
pointRadius,
mHasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
Log.w(TAG,
"Failed to stretch and rescale segments with single segments and no points",
@@ -537,16 +540,20 @@ public final class NotificationProgressBar extends ProgressBar implements
mParts,
mProgressDrawableParts,
progressFraction,
- width,
isStyledByProgress,
progressGap);
}
+ // Extend the first and last segments to fill the entire width.
+ p.first.getFirst().setStart(0);
+ p.first.getLast().setEnd(width);
+
if (DEBUG) {
Log.d(TAG, "Updating NotificationProgressDrawable parts");
}
mNotificationProgressDrawable.setParts(p.first);
- mAdjustedProgressFraction = p.second / width;
+ mAdjustedProgressFraction =
+ (p.second - mTrackerDrawWidth / 2F) / (width - mTrackerDrawWidth);
}
private void updateTrackerAndBarPos(int w, int h) {
@@ -607,7 +614,7 @@ public final class NotificationProgressBar extends ProgressBar implements
int available = w - mPaddingLeft - mPaddingRight;
final int trackerWidth = tracker.getIntrinsicWidth();
final int trackerHeight = tracker.getIntrinsicHeight();
- available -= ((mTrackerHeight <= 0) ? trackerWidth : mTrackerWidth);
+ available -= mTrackerDrawWidth;
final int trackerPos = (int) (progressFraction * available + 0.5f);
@@ -672,7 +679,7 @@ public final class NotificationProgressBar extends ProgressBar implements
canvas.translate(mPaddingLeft + mTrackerPos, mPaddingTop);
if (mTrackerHeight > 0) {
- canvas.clipRect(0, 0, mTrackerWidth, mTrackerHeight);
+ canvas.clipRect(0, 0, mTrackerDrawWidth, mTrackerHeight);
}
if (mTrackerDrawMatrix != null) {
@@ -751,6 +758,7 @@ public final class NotificationProgressBar extends ProgressBar implements
throw new IllegalArgumentException("Invalid progress : " + progress);
}
+
for (ProgressStyle.Point point : points) {
final int pos = point.getPosition();
if (pos < 0 || pos > progressMax) {
@@ -758,6 +766,19 @@ public final class NotificationProgressBar extends ProgressBar implements
}
}
+ // There should be no points at start or end. If there are, drop them with a warning.
+ points.removeIf(point -> {
+ final int pos = point.getPosition();
+ if (pos == 0) {
+ Log.w(TAG, "Dropping point at start");
+ return true;
+ } else if (pos == progressMax) {
+ Log.w(TAG, "Dropping point at end");
+ return true;
+ }
+ return false;
+ });
+
final Map<Integer, ProgressStyle.Segment> startToSegmentMap = generateStartToSegmentMap(
segments);
final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
@@ -891,12 +912,14 @@ public final class NotificationProgressBar extends ProgressBar implements
float segSegGap,
float segPointGap,
float pointRadius,
- boolean hasTrackerIcon
- ) {
+ boolean hasTrackerIcon,
+ int trackerDrawWidth) {
List<DrawablePart> drawableParts = new ArrayList<>();
- // generally, we will start drawing at (x, y) and end at (x+w, y)
- float x = (float) 0;
+ float available = totalWidth - trackerDrawWidth;
+ // Generally, we will start the first segment at (x+trackerDrawWidth/2, y) and end the last
+ // segment at (x+w-trackerDrawWidth/2, y)
+ float x = trackerDrawWidth / 2F;
final int nParts = parts.size();
for (int iPart = 0; iPart < nParts; iPart++) {
@@ -904,15 +927,14 @@ public final class NotificationProgressBar extends ProgressBar implements
final Part prevPart = iPart == 0 ? null : parts.get(iPart - 1);
final Part nextPart = iPart + 1 == nParts ? null : parts.get(iPart + 1);
if (part instanceof Segment segment) {
- final float segWidth = segment.mFraction * totalWidth;
+ final float segWidth = segment.mFraction * available;
// Advance the start position to account for a point immediately prior.
- final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap,
- iPart == 1);
+ final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap);
final float start = x + startOffset;
// Retract the end position to account for the padding and a point immediately
// after.
final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
- segSegGap, iPart == nParts - 2, hasTrackerIcon);
+ segSegGap, hasTrackerIcon);
final float end = x + segWidth - endOffset;
drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -927,16 +949,6 @@ public final class NotificationProgressBar extends ProgressBar implements
final float pointWidth = 2 * pointRadius;
float start = x - pointRadius;
float end = x + pointRadius;
- // Only shift the points right at the start/end.
- // For the points close to the start/end, the segment minimum width requirement
- // would take care of shifting them to be within the bounds.
- if (iPart == 0) {
- start = 0;
- end = pointWidth;
- } else if (iPart == nParts - 1) {
- start = totalWidth - pointWidth;
- end = totalWidth;
- }
drawableParts.add(new DrawablePoint(start, end, point.mColor));
}
@@ -945,16 +957,13 @@ public final class NotificationProgressBar extends ProgressBar implements
return drawableParts;
}
- private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
- boolean isSecondPart) {
+ private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap) {
if (!(prevPart instanceof Point)) return 0F;
- final float pointOffset = isSecondPart ? pointRadius : 0;
- return pointOffset + pointRadius + segPointGap;
+ return pointRadius + segPointGap;
}
private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
- float segPointGap, float segSegGap, boolean isSecondToLastPart,
- boolean hasTrackerIcon) {
+ float segPointGap, float segSegGap, boolean hasTrackerIcon) {
if (nextPart == null) return 0F;
if (nextPart instanceof Segment nextSeg) {
if (!seg.mFaded && nextSeg.mFaded) {
@@ -964,8 +973,7 @@ public final class NotificationProgressBar extends ProgressBar implements
return segSegGap;
}
- final float pointOffset = isSecondToLastPart ? pointRadius : 0;
- return segPointGap + pointRadius + pointOffset;
+ return segPointGap + pointRadius;
}
/**
@@ -980,7 +988,6 @@ public final class NotificationProgressBar extends ProgressBar implements
float segmentMinWidth,
float pointRadius,
float progressFraction,
- float totalWidth,
boolean isStyledByProgress,
float progressGap
) throws NotEnoughWidthToFitAllPartsException {
@@ -1003,7 +1010,6 @@ public final class NotificationProgressBar extends ProgressBar implements
parts,
drawableParts,
progressFraction,
- totalWidth,
isStyledByProgress,
progressGap);
}
@@ -1056,7 +1062,6 @@ public final class NotificationProgressBar extends ProgressBar implements
parts,
drawableParts,
progressFraction,
- totalWidth,
isStyledByProgress,
progressGap);
}
@@ -1071,11 +1076,12 @@ public final class NotificationProgressBar extends ProgressBar implements
List<Part> parts,
List<DrawablePart> drawableParts,
float progressFraction,
- float totalWidth,
boolean isStyledByProgress,
float progressGap
) {
- if (progressFraction == 1) return new Pair<>(drawableParts, totalWidth);
+ if (progressFraction == 1) {
+ return new Pair<>(drawableParts, drawableParts.getLast().getEnd());
+ }
int iPartFirstSegmentToStyle = -1;
int iPartSegmentToSplit = -1;
@@ -1162,14 +1168,15 @@ public final class NotificationProgressBar extends ProgressBar implements
float pointRadius,
boolean hasTrackerIcon,
float segmentMinWidth,
- boolean isStyledByProgress
+ boolean isStyledByProgress,
+ int trackerDrawWidth
) throws NotEnoughWidthToFitAllPartsException {
List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
progressMax);
List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
- segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon, trackerDrawWidth);
return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
- getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+ getProgressFraction(progressMax, progress), isStyledByProgress,
hasTrackerIcon ? 0F : segSegGap);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9f4cdc8e3ce..51049889ecd6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -9385,6 +9385,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.security.UpdateCertificateRevocationStatusJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d7ffcc54562c..17acf9aed278 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7352,6 +7352,21 @@
default on this device-->
<string-array translatable="false" name="config_notificationDefaultUnsupportedAdjustments" />
+ <!-- Preference name of bugreport-->
+ <string name="prefs_bugreport" translatable="false">bugreports</string>
+
+ <!-- key value of warning state stored in bugreport preference-->
+ <string name="key_warning_state" translatable="false">warning-state</string>
+
+ <!-- Bugreport warning dialog state unknown-->
+ <integer name="bugreport_state_unknown">0</integer>
+
+ <!-- Bugreport warning dialog state shows the warning dialog-->
+ <integer name="bugreport_state_show">1</integer>
+
+ <!-- Bugreport warning dialog state skips the warning dialog-->
+ <integer name="bugreport_state_hide">2</integer>
+
<!-- By default ActivityOptions#makeScaleUpAnimation is only used between activities. This
config enables OEMs to support its usage across tasks.-->
<bool name="config_enableCrossTaskScaleUpAnimation">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6701e63c4f90..cc2897a2779e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5906,6 +5906,12 @@
<java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_title" />
<java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_text" />
+ <java-symbol type="string" name="prefs_bugreport" />
+ <java-symbol type="string" name="key_warning_state" />
+ <java-symbol type="integer" name="bugreport_state_unknown" />
+ <java-symbol type="integer" name="bugreport_state_show" />
+ <java-symbol type="integer" name="bugreport_state_hide" />
+
<!-- Enable OEMs to support scale up anim across tasks.-->
<java-symbol type="bool" name="config_enableCrossTaskScaleUpAnimation" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ca6ad6fae46e..f89e4416ce78 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2532,6 +2532,46 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_addProgressPoint_dropsZeroPoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Points should not be a negative integer.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(0));
+
+ // THEN
+ assertThat(progressStyle.getProgressPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressPoint_dropsZeroPoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Points should not be a negative integer.
+ progressStyle
+ .setProgressPoints(List.of(new Notification.ProgressStyle.Point(0)));
+
+ // THEN
+ assertThat(progressStyle.getProgressPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_ignoresPointsAtMax() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+ // Points should not larger than progress maximum.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(100));
+
+ // THEN
+ assertThat(progressStyle.createProgressModel(Color.BLUE, Color.RED).getPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2573,14 +2613,14 @@ public class NotificationTest {
// THEN
assertThat(progressStyle.createProgressModel(defaultProgressColor, backgroundColor)
.getPoints()).isEqualTo(
- List.of(new Notification.ProgressStyle.Point(0)
- .setColor(expectedProgressColor),
- new Notification.ProgressStyle.Point(20)
+ List.of(new Notification.ProgressStyle.Point(20)
.setColor(expectedProgressColor),
new Notification.ProgressStyle.Point(50)
.setColor(expectedProgressColor),
new Notification.ProgressStyle.Point(70)
- .setColor(expectedProgressColor)
+ .setColor(expectedProgressColor),
+ new Notification.ProgressStyle.Point(80)
+ .setColor(expectedProgressColor)
)
);
}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 8fa510381060..dc2f0a69375d 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,8 +307,10 @@ public class DisplayManagerGlobalTest {
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
- assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
- .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
+ assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
+ mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+ 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index 84ff40f0dcf0..116dc124c902 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -266,4 +266,25 @@ public class WindowTokenClientControllerTest {
verify(mWindowTokenClient).onWindowTokenRemoved();
}
+
+ @Test
+ public void testOnWindowConfigurationChanged_propagatedToCorrectToken() throws RemoteException {
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToDisplayContent(any(), any(), anyInt());
+
+ mController.onWindowConfigurationChanged(mWindowTokenClient, mConfiguration,
+ DEFAULT_DISPLAY + 1);
+
+ // Not propagated before attaching
+ verify(mWindowTokenClient, never()).onConfigurationChanged(mConfiguration,
+ DEFAULT_DISPLAY + 1);
+
+ assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
+
+ mController.onWindowConfigurationChanged(mWindowTokenClient, mConfiguration,
+ DEFAULT_DISPLAY + 1);
+
+ // Now that's attached, propagating it.
+ verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9baa31faea08..282886af9ef8 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -121,18 +121,20 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, Color.RED)));
+ List.of(new DrawableSegment(10, 310, Color.RED)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -141,14 +143,14 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedRed = 0x80FF0000;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, fadedRed, true)));
+ List.of(new DrawableSegment(10, 310, fadedRed, true)));
- assertThat(p.second).isEqualTo(0);
+ assertThat(p.second).isEqualTo(10);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -168,18 +170,20 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, Color.RED)));
+ List.of(new DrawableSegment(10, 310, Color.RED)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -188,9 +192,9 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(p.second).isEqualTo(300);
+ assertThat(p.second).isEqualTo(310);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -219,6 +223,42 @@ public class NotificationProgressBarTest {
progressMax);
}
+ @Test
+ public void processAndConvertToParts_pointPositionIsZero() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(Color.RED));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ // Point at the start is dropped.
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_pointPositionAtMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(100).setColor(Color.RED));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ // Point at the end is dropped.
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+ }
+
@Test(expected = IllegalArgumentException.class)
public void processAndConvertToParts_pointPositionAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
@@ -249,18 +289,20 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, Color.BLUE)));
+ List.of(new DrawableSegment(10, 310, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -269,15 +311,15 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedBlue = 0x800000FF;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 180, Color.BLUE),
- new DrawableSegment(180, 300, fadedBlue, true)));
+ List.of(new DrawableSegment(10, 190, Color.BLUE),
+ new DrawableSegment(190, 310, fadedBlue, true)));
- assertThat(p.second).isEqualTo(180);
+ assertThat(p.second).isEqualTo(190);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -299,19 +341,21 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 146, Color.RED),
- new DrawableSegment(150, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 156, Color.RED),
+ new DrawableSegment(160, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -319,15 +363,15 @@ public class NotificationProgressBarTest {
boolean isStyledByProgress = true;
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
- expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(0, 146, Color.RED),
- new DrawableSegment(150, 180, Color.GREEN),
- new DrawableSegment(180, 300, fadedGreen, true)));
+ expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(10, 156, Color.RED),
+ new DrawableSegment(160, 190, Color.GREEN),
+ new DrawableSegment(190, 310, fadedGreen, true)));
- assertThat(p.second).isEqualTo(180);
+ assertThat(p.second).isEqualTo(190);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -353,10 +397,12 @@ public class NotificationProgressBarTest {
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = false;
+ int trackerDrawWidth = 0;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -368,7 +414,7 @@ public class NotificationProgressBarTest {
boolean isStyledByProgress = true;
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
@@ -409,26 +455,28 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 35, Color.BLUE),
- new DrawablePoint(39, 51, Color.RED),
- new DrawableSegment(55, 65, Color.BLUE),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 170, Color.BLUE),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 215, Color.BLUE),
- new DrawablePoint(219, 231, Color.YELLOW),
- new DrawableSegment(235, 300, Color.BLUE)));
+ List.of(new DrawableSegment(10, 45, Color.BLUE),
+ new DrawablePoint(49, 61, Color.RED),
+ new DrawableSegment(65, 75, Color.BLUE),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 180, Color.BLUE),
+ new DrawablePoint(184, 196, Color.BLUE),
+ new DrawableSegment(200, 225, Color.BLUE),
+ new DrawablePoint(229, 241, Color.YELLOW),
+ new DrawableSegment(245, 310, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -437,23 +485,23 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedBlue = 0x800000FF;
int fadedYellow = 0x80FFFF00;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
- new DrawablePoint(38.219177F, 50.219177F, Color.RED),
- new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
- new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
- new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
- new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
- new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
- new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
- new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
-
- assertThat(p.second).isEqualTo(182.38356F);
+ List.of(new DrawableSegment(10, 44.219177F, Color.BLUE),
+ new DrawablePoint(48.219177F, 60.219177F, Color.RED),
+ new DrawableSegment(64.219177F, 80.21918F, Color.BLUE),
+ new DrawablePoint(84.21918F, 96.21918F, Color.BLUE),
+ new DrawableSegment(100.21918F, 182.38356F, Color.BLUE),
+ new DrawablePoint(186.38356F, 198.38356F, Color.BLUE),
+ new DrawableSegment(202.38356F, 227.0137F, fadedBlue, true),
+ new DrawablePoint(231.0137F, 243.0137F, fadedYellow),
+ new DrawableSegment(247.0137F, 310F, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(192.38356F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -488,102 +536,29 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
- float segSegGap = 4;
- float segPointGap = 4;
- float pointRadius = 6;
- boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts =
- NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
-
- List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
- new DrawableSegment(55, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 215, Color.GREEN),
- new DrawablePoint(219, 231, Color.YELLOW),
- new DrawableSegment(235, 300, Color.GREEN)));
-
- assertThat(drawableParts).isEqualTo(expectedDrawableParts);
-
- float segmentMinWidth = 16;
- boolean isStyledByProgress = true;
-
- Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
- parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
-
- // Colors with 50% opacity
- int fadedGreen = 0x8000FF00;
- int fadedYellow = 0x80FFFF00;
- expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.095238F, Color.RED),
- new DrawablePoint(38.095238F, 50.095238F, Color.RED),
- new DrawableSegment(54.095238F, 70.09524F, Color.RED),
- new DrawablePoint(74.09524F, 86.09524F, Color.BLUE),
- new DrawableSegment(90.09524F, 148.9524F, Color.RED),
- new DrawableSegment(152.95238F, 172.7619F, Color.GREEN),
- new DrawablePoint(176.7619F, 188.7619F, Color.BLUE),
- new DrawableSegment(192.7619F, 217.33333F, fadedGreen, true),
- new DrawablePoint(221.33333F, 233.33333F, fadedYellow),
- new DrawableSegment(237.33333F, 299.99997F, fadedGreen, true)));
-
- assertThat(p.second).isEqualTo(182.7619F);
- assertThat(p.first).isEqualTo(expectedDrawableParts);
- }
-
- @Test
- public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
- throws NotEnoughWidthToFitAllPartsException {
- List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
- segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
- List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.RED));
- points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
- points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
- points.add(new ProgressStyle.Point(100).setColor(Color.YELLOW));
- int progress = 60;
- int progressMax = 100;
-
- List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
- points, progress, progressMax);
-
- List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.RED),
- new Segment(0.25f, Color.RED),
- new Point(Color.BLUE),
- new Segment(0.25f, Color.RED),
- new Segment(0.10f, Color.GREEN),
- new Point(Color.BLUE),
- new Segment(0.4f, Color.GREEN),
- new Point(Color.YELLOW)));
-
- assertThat(parts).isEqualTo(expectedParts);
-
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.RED),
- new DrawableSegment(16, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 284, Color.GREEN),
- new DrawablePoint(288, 300, Color.YELLOW)));
+ List.of(new DrawableSegment(10, 45, Color.RED),
+ new DrawablePoint(49, 61, Color.RED),
+ new DrawableSegment(65, 75, Color.RED),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 156, Color.RED),
+ new DrawableSegment(160, 180, Color.GREEN),
+ new DrawablePoint(184, 196, Color.BLUE),
+ new DrawableSegment(200, 225, Color.GREEN),
+ new DrawablePoint(229, 241, Color.YELLOW),
+ new DrawableSegment(245, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -592,22 +567,24 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
int fadedYellow = 0x80FFFF00;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.RED),
- new DrawableSegment(16, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 284, fadedGreen, true),
- new DrawablePoint(288, 300, fadedYellow)));
-
- assertThat(p.second).isEqualTo(180);
+ List.of(new DrawableSegment(10, 44.095238F, Color.RED),
+ new DrawablePoint(48.095238F, 60.095238F, Color.RED),
+ new DrawableSegment(64.095238F, 80.09524F, Color.RED),
+ new DrawablePoint(84.09524F, 96.09524F, Color.BLUE),
+ new DrawableSegment(100.09524F, 158.9524F, Color.RED),
+ new DrawableSegment(162.95238F, 182.7619F, Color.GREEN),
+ new DrawablePoint(186.7619F, 198.7619F, Color.BLUE),
+ new DrawableSegment(202.7619F, 227.33333F, fadedGreen, true),
+ new DrawablePoint(231.33333F, 243.33333F, fadedYellow),
+ new DrawableSegment(247.33333F, 309.99997F, fadedGreen, true)));
+
+ assertThat(p.second).isEqualTo(192.7619F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -644,27 +621,29 @@ public class NotificationProgressBarTest {
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, -7, Color.RED),
- new DrawablePoint(-3, 9, Color.RED),
- new DrawableSegment(13, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 287, Color.GREEN),
- new DrawablePoint(291, 303, Color.YELLOW),
- new DrawableSegment(307, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 3, Color.RED),
+ new DrawablePoint(7, 19, Color.RED),
+ new DrawableSegment(23, 75, Color.RED),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 156, Color.RED),
+ new DrawableSegment(160, 180, Color.GREEN),
+ new DrawablePoint(184, 196, Color.BLUE),
+ new DrawableSegment(200, 297, Color.GREEN),
+ new DrawablePoint(301, 313, Color.YELLOW),
+ new DrawableSegment(317, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -673,24 +652,24 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
int fadedYellow = 0x80FFFF00;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 16, Color.RED),
- new DrawablePoint(20, 32, Color.RED),
- new DrawableSegment(36, 78.02409F, Color.RED),
- new DrawablePoint(82.02409F, 94.02409F, Color.BLUE),
- new DrawableSegment(98.02409F, 146.55421F, Color.RED),
- new DrawableSegment(150.55421F, 169.44579F, Color.GREEN),
- new DrawablePoint(173.44579F, 185.44579F, Color.BLUE),
- new DrawableSegment(189.44579F, 264, fadedGreen, true),
- new DrawablePoint(268, 280, fadedYellow),
- new DrawableSegment(284, 300, fadedGreen, true)));
-
- assertThat(p.second).isEqualTo(179.44579F);
+ List.of(new DrawableSegment(10, 26, Color.RED),
+ new DrawablePoint(30, 42, Color.RED),
+ new DrawableSegment(46, 88.02409F, Color.RED),
+ new DrawablePoint(92.02409F, 104.02409F, Color.BLUE),
+ new DrawableSegment(108.02409F, 156.55421F, Color.RED),
+ new DrawableSegment(160.55421F, 179.44579F, Color.GREEN),
+ new DrawablePoint(183.44579F, 195.44579F, Color.BLUE),
+ new DrawableSegment(199.44579F, 274, fadedGreen, true),
+ new DrawablePoint(278, 290, fadedYellow),
+ new DrawableSegment(294, 310, fadedGreen, true)));
+
+ assertThat(p.second).isEqualTo(189.44579F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -711,31 +690,38 @@ public class NotificationProgressBarTest {
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
- new Segment(0.10f, Color.RED), new Point(Color.BLUE),
- new Segment(0.25f, Color.RED), new Segment(0.25f, Color.GREEN),
- new Point(Color.YELLOW), new Segment(0.25f, Color.GREEN)));
+ List.of(new Segment(0.15f, Color.RED),
+ new Point(Color.RED),
+ new Segment(0.10f, Color.RED),
+ new Point(Color.BLUE),
+ new Segment(0.25f, Color.RED),
+ new Segment(0.25f, Color.GREEN),
+ new Point(Color.YELLOW),
+ new Segment(0.25f, Color.GREEN)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
- new DrawableSegment(55, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 215, Color.GREEN),
- new DrawablePoint(219, 231, Color.YELLOW),
- new DrawableSegment(235, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 45, Color.RED),
+ new DrawablePoint(49, 61, Color.RED),
+ new DrawableSegment(65, 75, Color.RED),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 156, Color.RED),
+ new DrawableSegment(160, 225, Color.GREEN),
+ new DrawablePoint(229, 241, Color.YELLOW),
+ new DrawableSegment(245, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -744,34 +730,34 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.296295F, Color.RED),
- new DrawablePoint(38.296295F, 50.296295F, Color.RED),
- new DrawableSegment(54.296295F, 70.296295F, Color.RED),
- new DrawablePoint(74.296295F, 86.296295F, Color.BLUE),
- new DrawableSegment(90.296295F, 149.62962F, Color.RED),
- new DrawableSegment(153.62962F, 216.8148F, Color.GREEN),
- new DrawablePoint(220.81482F, 232.81482F, Color.YELLOW),
- new DrawableSegment(236.81482F, 300, Color.GREEN)));
-
- assertThat(p.second).isEqualTo(182.9037F);
+ List.of(new DrawableSegment(10, 44.296295F, Color.RED),
+ new DrawablePoint(48.296295F, 60.296295F, Color.RED),
+ new DrawableSegment(64.296295F, 80.296295F, Color.RED),
+ new DrawablePoint(84.296295F, 96.296295F, Color.BLUE),
+ new DrawableSegment(100.296295F, 159.62962F, Color.RED),
+ new DrawableSegment(163.62962F, 226.8148F, Color.GREEN),
+ new DrawablePoint(230.81482F, 242.81482F, Color.YELLOW),
+ new DrawableSegment(246.81482F, 310, Color.GREEN)));
+
+ assertThat(p.second).isEqualTo(192.9037F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
- // The only difference from the `zeroWidthDrawableSegment` test below is the longer
+ // The only difference from the `segmentWidthAtMin` test below is the longer
// segmentMinWidth (= 16dp).
@Test
- public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+ public void maybeStretchAndRescaleSegments_segmentWidthBelowMin()
throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(200).setColor(Color.BLUE));
int progress = 1000;
int progressMax = 1000;
@@ -779,28 +765,32 @@ public class NotificationProgressBarTest {
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
- new Segment(0.2f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+ List.of(new Segment(0.2f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
new Segment(0.4f, Color.BLUE)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 200;
+ float drawableWidth = 220;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 16, Color.BLUE),
- new DrawableSegment(20, 56, Color.BLUE),
- new DrawableSegment(60, 116, Color.BLUE),
- new DrawableSegment(120, 200, Color.BLUE)));
+ List.of(new DrawableSegment(10, 40, Color.BLUE),
+ new DrawablePoint(44, 56, Color.BLUE),
+ new DrawableSegment(60, 66, Color.BLUE),
+ new DrawableSegment(70, 126, Color.BLUE),
+ new DrawableSegment(130, 210, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -809,30 +799,31 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 32, Color.BLUE),
- new DrawableSegment(36, 69.41936F, Color.BLUE),
- new DrawableSegment(73.41936F, 124.25807F, Color.BLUE),
- new DrawableSegment(128.25807F, 200, Color.BLUE)));
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(10, 38.81356F, Color.BLUE),
+ new DrawablePoint(42.81356F, 54.81356F, Color.BLUE),
+ new DrawableSegment(58.81356F, 74.81356F, Color.BLUE),
+ new DrawableSegment(78.81356F, 131.42374F, Color.BLUE),
+ new DrawableSegment(135.42374F, 210, Color.BLUE)));
- assertThat(p.second).isEqualTo(200);
+ assertThat(p.second).isEqualTo(210);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
- // The only difference from the `negativeWidthDrawableSegment` test above is the shorter
+ // The only difference from the `segmentWidthBelowMin` test above is the shorter
// segmentMinWidth (= 10dp).
@Test
- public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+ public void maybeStretchAndRescaleSegments_segmentWidthAtMin()
throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(200).setColor(Color.BLUE));
int progress = 1000;
int progressMax = 1000;
@@ -840,28 +831,32 @@ public class NotificationProgressBarTest {
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
- new Segment(0.2f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+ List.of(new Segment(0.2f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
new Segment(0.4f, Color.BLUE)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 200;
+ float drawableWidth = 220;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 16, Color.BLUE),
- new DrawableSegment(20, 56, Color.BLUE),
- new DrawableSegment(60, 116, Color.BLUE),
- new DrawableSegment(120, 200, Color.BLUE)));
+ List.of(new DrawableSegment(10, 40, Color.BLUE),
+ new DrawablePoint(44, 56, Color.BLUE),
+ new DrawableSegment(60, 66, Color.BLUE),
+ new DrawableSegment(70, 126, Color.BLUE),
+ new DrawableSegment(130, 210, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -870,15 +865,16 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 26, Color.BLUE),
- new DrawableSegment(30, 64.169014F, Color.BLUE),
- new DrawableSegment(68.169014F, 120.92958F, Color.BLUE),
- new DrawableSegment(124.92958F, 200, Color.BLUE)));
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(10, 39.411766F, Color.BLUE),
+ new DrawablePoint(43.411766F, 55.411766F, Color.BLUE),
+ new DrawableSegment(59.411766F, 69.411766F, Color.BLUE),
+ new DrawableSegment(73.411766F, 128.05884F, Color.BLUE),
+ new DrawableSegment(132.05882F, 210, Color.BLUE)));
- assertThat(p.second).isEqualTo(200);
+ assertThat(p.second).isEqualTo(210);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -886,12 +882,12 @@ public class NotificationProgressBarTest {
public void maybeStretchAndRescaleSegments_noStretchingNecessary()
throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(100).setColor(Color.BLUE));
int progress = 1000;
int progressMax = 1000;
@@ -899,28 +895,32 @@ public class NotificationProgressBarTest {
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
- new Segment(0.1f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+ List.of(new Segment(0.1f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.2f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
new Segment(0.4f, Color.BLUE)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 200;
+ float drawableWidth = 220;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 36, Color.BLUE),
- new DrawableSegment(40, 56, Color.BLUE),
- new DrawableSegment(60, 116, Color.BLUE),
- new DrawableSegment(120, 200, Color.BLUE)));
+ List.of(new DrawableSegment(10, 20, Color.BLUE),
+ new DrawablePoint(24, 36, Color.BLUE),
+ new DrawableSegment(40, 66, Color.BLUE),
+ new DrawableSegment(70, 126, Color.BLUE),
+ new DrawableSegment(130, 210, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -929,9 +929,9 @@ public class NotificationProgressBarTest {
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(p.second).isEqualTo(200);
+ assertThat(p.second).isEqualTo(210);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -951,10 +951,10 @@ public class NotificationProgressBarTest {
segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(orange));
points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(10).setColor(orange));
points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
- points.add(new ProgressStyle.Point(100).setColor(orange));
+ points.add(new ProgressStyle.Point(90).setColor(orange));
int progress = 50;
int progressMax = 100;
@@ -962,10 +962,10 @@ public class NotificationProgressBarTest {
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(orange),
- new Segment(0.01f, orange),
+ List.of(new Segment(0.01f, orange),
new Point(Color.BLUE),
new Segment(0.09f, orange),
+ new Point(orange),
new Segment(0.1f, Color.YELLOW),
new Segment(0.1f, Color.BLUE),
new Segment(0.1f, Color.GREEN),
@@ -976,21 +976,23 @@ public class NotificationProgressBarTest {
new Segment(0.1f, Color.YELLOW),
new Segment(0.1f, Color.BLUE),
new Segment(0.1f, Color.GREEN),
- new Segment(0.1f, Color.RED),
- new Point(orange)));
+ new Point(orange),
+ new Segment(0.1f, Color.RED)));
assertThat(parts).isEqualTo(expectedParts);
// For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
- float drawableWidth = 299;
+ float drawableWidth = 319;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
// Skips the validation of the intermediate list of DrawableParts.
@@ -999,7 +1001,7 @@ public class NotificationProgressBarTest {
NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
}
@Test
@@ -1015,11 +1017,12 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
float segmentMinWidth = 16;
boolean isStyledByProgress = true;
@@ -1036,24 +1039,24 @@ public class NotificationProgressBarTest {
pointRadius,
hasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ trackerDrawWidth);
// Colors with 50% opacity
int fadedBlue = 0x800000FF;
int fadedYellow = 0x80FFFF00;
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
- new DrawablePoint(38.219177F, 50.219177F, Color.RED),
- new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
- new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
- new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
- new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
- new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
- new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
- new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
-
- assertThat(p.second).isEqualTo(182.38356F);
+ List.of(new DrawableSegment(10, 44.219177F, Color.BLUE),
+ new DrawablePoint(48.219177F, 60.219177F, Color.RED),
+ new DrawableSegment(64.219177F, 80.21918F, Color.BLUE),
+ new DrawablePoint(84.21918F, 96.21918F, Color.BLUE),
+ new DrawableSegment(100.21918F, 182.38356F, Color.BLUE),
+ new DrawablePoint(186.38356F, 198.38356F, Color.BLUE),
+ new DrawableSegment(202.38356F, 227.0137F, fadedBlue, true),
+ new DrawablePoint(231.0137F, 243.0137F, fadedYellow),
+ new DrawableSegment(247.0137F, 310F, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(192.38356F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -1065,11 +1068,12 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- float drawableWidth = 100;
+ float drawableWidth = 120;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
float segmentMinWidth = 16;
boolean isStyledByProgress = true;
@@ -1086,16 +1090,16 @@ public class NotificationProgressBarTest {
pointRadius,
hasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ trackerDrawWidth);
- // Colors with 50%f opacity
+ // Colors with 50% opacity
int fadedBlue = 0x800000FF;
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
- new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+ List.of(new DrawableSegment(10, 70F, Color.BLUE),
+ new DrawableSegment(70F, 110, fadedBlue, true)));
- assertThat(p.second).isWithin(1e-5f).of(60);
+ assertThat(p.second).isEqualTo(70);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index e1f5b1c2e4a4..140d268e855b 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -90,7 +90,7 @@ public class NotificationProgressModelTest {
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
new Notification.ProgressStyle.Segment(50).setColor(Color.LTGRAY));
final List<Notification.ProgressStyle.Point> points = List.of(
- new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(1).setColor(Color.RED),
new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
@@ -121,7 +121,7 @@ public class NotificationProgressModelTest {
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
final List<Notification.ProgressStyle.Point> points = List.of(
- new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(1).setColor(Color.RED),
new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 13d0169c47c5..a08f88a5b937 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -177,3 +177,10 @@ flag {
description: "Factor task-view state tracking out of taskviewtransitions"
bug: "384976265"
}
+
+flag {
+ name: "enable_bubble_bar_on_phones"
+ namespace: "multitasking"
+ description: "Try out bubble bar on phones"
+ bug: "394869612"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index bce6c5999a75..a32ec221e08a 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -61,7 +61,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.util.Optional
@@ -133,7 +132,7 @@ class BubbleControllerBubbleBarTest {
mainExecutor,
bgExecutor,
)
- bubbleController.asBubbles().setSysuiProxy(Mockito.mock(SysuiProxy::class.java))
+ bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>())
shellInit.init()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 88bfeb21bb74..e865111e59dc 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -50,10 +50,10 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
@@ -635,7 +635,7 @@ class BubbleStackViewTest {
@Test
fun removeFromWindow_stopMonitoringSwipeUpGesture() {
- bubbleStackView = Mockito.spy(bubbleStackView)
+ bubbleStackView = spy(bubbleStackView)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
// No way to add to window in the test environment right now so just pretend
bubbleStackView.onDetachedFromWindow()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
index af238d033aee..3499ee32e649 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
@@ -29,7 +29,8 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
/** Test for [UiEventSubject] */
@@ -130,10 +131,10 @@ class UiEventSubjectTest {
}
private fun createBubble(appUid: Int, packageName: String, instanceId: InstanceId): Bubble {
- return mock(Bubble::class.java).apply {
- whenever(getAppUid()).thenReturn(appUid)
- whenever(getPackageName()).thenReturn(packageName)
- whenever(getInstanceId()).thenReturn(instanceId)
+ return mock<Bubble>() {
+ on { getAppUid() } doReturn appUid
+ on { getPackageName() } doReturn packageName
+ on { getInstanceId() } doReturn instanceId
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index c022a298e972..7b5831376dc0 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -73,7 +73,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -127,7 +126,7 @@ class BubbleBarLayerViewTest {
mainExecutor,
bgExecutor,
)
- bubbleController.asBubbles().setSysuiProxy(mock(SysuiProxy::class.java))
+ bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>())
// Flush so that proxy gets set
mainExecutor.flushAll()
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a2231dd64112..1b7daa87064a 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -290,7 +290,7 @@
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
<string name="fullscreen_text">Fullscreen</string>
<!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
- <string name="desktop_text">Desktop Mode</string>
+ <string name="desktop_text">Desktop View</string>
<!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
<string name="split_screen_text">Split Screen</string>
<!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
@@ -316,7 +316,7 @@
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
<string name="collapse_menu_text">Close Menu</string>
<!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] -->
- <string name="desktop_mode_app_header_chip_text">Open Menu</string>
+ <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop View)</string>
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
<!-- Maximize menu snap buttons string. -->
@@ -342,10 +342,10 @@
<!-- Accessibility text for the Maximize Menu's snap maximize/restore [CHAR LIMIT=NONE] -->
<string name="desktop_mode_a11y_action_maximize_restore">Maximize or restore window size</string>
- <!-- Accessibility action replacement for caption handle menu split screen button [CHAR LIMIT=NONE] -->
- <string name="app_handle_menu_talkback_split_screen_mode_button_text">Enter split screen mode</string>
- <!-- Accessibility action replacement for caption handle menu enter desktop mode button [CHAR LIMIT=NONE] -->
- <string name="app_handle_menu_talkback_desktop_mode_button_text">Enter desktop windowing mode</string>
+ <!-- Accessibility action replacement for caption handle app chip buttons [CHAR LIMIT=NONE] -->
+ <string name="app_handle_chip_accessibility_announce">Open Menu</string>
+ <!-- Accessibility action replacement for caption handle menu buttons [CHAR LIMIT=NONE] -->
+ <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop View">%1$s</xliff:g></string>
<!-- Accessibility action replacement for maximize menu enter snap left button [CHAR LIMIT=NONE] -->
<string name="maximize_menu_talkback_action_snap_left_text">Resize window to left</string>
<!-- Accessibility action replacement for maximize menu enter snap right button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 0b1f76f5ce0e..d280083ae7f5 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -17,4 +17,23 @@
<resources>
<dimen name="floating_dismiss_icon_size">32dp</dimen>
<dimen name="floating_dismiss_background_size">96dp</dimen>
+
+ <!-- Bubble drag zone dimensions -->
+ <dimen name="drag_zone_dismiss_fold">140dp</dimen>
+ <dimen name="drag_zone_dismiss_tablet">200dp</dimen>
+ <dimen name="drag_zone_bubble_fold">140dp</dimen>
+ <dimen name="drag_zone_bubble_tablet">200dp</dimen>
+ <dimen name="drag_zone_full_screen_width">512dp</dimen>
+ <dimen name="drag_zone_full_screen_height">44dp</dimen>
+ <dimen name="drag_zone_desktop_window_width">880dp</dimen>
+ <dimen name="drag_zone_desktop_window_height">300dp</dimen>
+ <dimen name="drag_zone_desktop_window_expanded_view_width">200dp</dimen>
+ <dimen name="drag_zone_desktop_window_expanded_view_height">350dp</dimen>
+ <dimen name="drag_zone_split_from_bubble_height">100dp</dimen>
+ <dimen name="drag_zone_split_from_bubble_width">60dp</dimen>
+ <dimen name="drag_zone_h_split_from_expanded_view_width">60dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_width">200dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_height_tablet">285dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_height_fold_tall">150dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen>
</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index 35802c936361..909e9d2c4428 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -16,11 +16,15 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
import android.graphics.Rect
+import androidx.annotation.DimenRes
+import com.android.wm.shell.shared.R
import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
/** A class for creating drag zones for dragging bubble objects or dragging into bubbles. */
class DragZoneFactory(
+ private val context: Context,
private val deviceConfig: DeviceConfig,
private val splitScreenModeChecker: SplitScreenModeChecker,
private val desktopWindowModeChecker: DesktopWindowModeChecker,
@@ -29,23 +33,65 @@ class DragZoneFactory(
private val windowBounds: Rect
get() = deviceConfig.windowBounds
- // TODO b/393172431: move these to xml
- private val dismissDragZoneSize = if (deviceConfig.isSmallTablet) 140 else 200
- private val bubbleDragZoneTabletSize = 200
- private val bubbleDragZoneFoldableSize = 140
- private val fullScreenDragZoneWidth = 512
- private val fullScreenDragZoneHeight = 44
- private val desktopWindowDragZoneWidth = 880
- private val desktopWindowDragZoneHeight = 300
- private val desktopWindowFromExpandedViewDragZoneWidth = 200
- private val desktopWindowFromExpandedViewDragZoneHeight = 350
- private val splitFromBubbleDragZoneHeight = 100
- private val splitFromBubbleDragZoneWidth = 60
- private val hSplitFromExpandedViewDragZoneWidth = 60
- private val vSplitFromExpandedViewDragZoneWidth = 200
- private val vSplitFromExpandedViewDragZoneHeightTablet = 285
- private val vSplitFromExpandedViewDragZoneHeightFoldTall = 150
- private val vSplitFromExpandedViewDragZoneHeightFoldShort = 100
+ private var dismissDragZoneSize = 0
+ private var bubbleDragZoneTabletSize = 0
+ private var bubbleDragZoneFoldableSize = 0
+ private var fullScreenDragZoneWidth = 0
+ private var fullScreenDragZoneHeight = 0
+ private var desktopWindowDragZoneWidth = 0
+ private var desktopWindowDragZoneHeight = 0
+ private var desktopWindowFromExpandedViewDragZoneWidth = 0
+ private var desktopWindowFromExpandedViewDragZoneHeight = 0
+ private var splitFromBubbleDragZoneHeight = 0
+ private var splitFromBubbleDragZoneWidth = 0
+ private var hSplitFromExpandedViewDragZoneWidth = 0
+ private var vSplitFromExpandedViewDragZoneWidth = 0
+ private var vSplitFromExpandedViewDragZoneHeightTablet = 0
+ private var vSplitFromExpandedViewDragZoneHeightFoldTall = 0
+ private var vSplitFromExpandedViewDragZoneHeightFoldShort = 0
+
+ init {
+ onConfigurationUpdated()
+ }
+
+ /** Updates all dimensions after a configuration change. */
+ fun onConfigurationUpdated() {
+ dismissDragZoneSize =
+ if (deviceConfig.isSmallTablet) {
+ context.resolveDimension(R.dimen.drag_zone_dismiss_fold)
+ } else {
+ context.resolveDimension(R.dimen.drag_zone_dismiss_tablet)
+ }
+ bubbleDragZoneTabletSize = context.resolveDimension(R.dimen.drag_zone_bubble_tablet)
+ bubbleDragZoneFoldableSize = context.resolveDimension(R.dimen.drag_zone_bubble_fold)
+ fullScreenDragZoneWidth = context.resolveDimension(R.dimen.drag_zone_full_screen_width)
+ fullScreenDragZoneHeight = context.resolveDimension(R.dimen.drag_zone_full_screen_height)
+ desktopWindowDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_width)
+ desktopWindowDragZoneHeight =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_height)
+ desktopWindowFromExpandedViewDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_width)
+ desktopWindowFromExpandedViewDragZoneHeight =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_height)
+ splitFromBubbleDragZoneHeight =
+ context.resolveDimension(R.dimen.drag_zone_split_from_bubble_height)
+ splitFromBubbleDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_split_from_bubble_width)
+ hSplitFromExpandedViewDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_h_split_from_expanded_view_width)
+ vSplitFromExpandedViewDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_width)
+ vSplitFromExpandedViewDragZoneHeightTablet =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_tablet)
+ vSplitFromExpandedViewDragZoneHeightFoldTall =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall)
+ vSplitFromExpandedViewDragZoneHeightFoldShort =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short)
+ }
+
+ private fun Context.resolveDimension(@DimenRes dimension: Int) =
+ resources.getDimensionPixelSize(dimension)
/**
* Creates the list of drag zones for the dragged object.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index f234ff5c2c84..c545d3001cc7 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
+import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
import android.window.DesktopModeFlags
import com.android.internal.R
import com.android.window.flags.Flags
@@ -59,13 +60,16 @@ class DesktopModeCompatPolicy(private val context: Context) {
* The treatment is enabled when all the of the following is true:
* * Any flags to forcibly consume caption insets are enabled.
* * Top activity have configuration coupled with insets.
- * * Task is not resizeable.
+ * * Task is not resizeable or [ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS]
+ * is enabled.
*/
fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean =
Flags.excludeCaptionFromAppBounds()
&& isAnyForceConsumptionFlagsEnabled()
&& taskInfo.topActivityInfo?.let {
- isInsetsCoupledWithConfiguration(it) && !taskInfo.isResizeable
+ isInsetsCoupledWithConfiguration(it) && (!taskInfo.isResizeable || it.isChangeEnabled(
+ OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
+ ))
} ?: false
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b2b99d648bf4..b6012378e4d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -914,12 +914,15 @@ public abstract class WMShellModule {
Context context,
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
InteractionJankMonitor interactionJankMonitor) {
return ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue()
? new SpringDragToDesktopTransitionHandler(
- context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+ context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+ interactionJankMonitor)
: new DefaultDragToDesktopTransitionHandler(
- context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+ context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+ interactionJankMonitor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 7491abd4248b..531304d6922a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1659,11 +1659,16 @@ class DesktopTasksController(
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
+
+ // If the wallpaper activity for this display already exists, let's reorder it to top.
+ val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
+ if (wallpaperActivityToken != null) {
+ wct.reorder(wallpaperActivityToken, /* onTop= */ true)
+ return
+ }
+
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- if (
- desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
- Flags.enablePerDisplayDesktopWallpaperActivity()
- ) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
index a5ba6612bb1a..c10752d36bf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -90,6 +90,11 @@ class DesktopUserRepositories(
return desktopRepoByUserId.getOrCreate(profileId)
}
+ fun getUserIdForProfile(profileId: Int): Int {
+ if (userIdToProfileIdsMap[userId]?.contains(profileId) == true) return userId
+ else return profileId
+ }
+
/** Dumps [DesktopRepository] for each user. */
fun dump(pw: PrintWriter, prefix: String) {
desktopRepoByUserId.forEach { key, value ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2ac76f319d32..8194d3cab445 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -70,6 +70,7 @@ sealed class DragToDesktopTransitionHandler(
private val context: Context,
private val transitions: Transitions,
private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val desktopUserRepositories: DesktopUserRepositories,
protected val interactionJankMonitor: InteractionJankMonitor,
protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {
@@ -127,15 +128,18 @@ sealed class DragToDesktopTransitionHandler(
pendingIntentCreatorBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
- val taskUser = UserHandle.of(taskInfo.userId)
+ // If we are launching home for a profile of a user, just use the [userId] of that user
+ // instead of the [profileId] to create the context.
+ val userToLaunchWith =
+ UserHandle.of(desktopUserRepositories.getUserIdForProfile(taskInfo.userId))
val pendingIntent =
PendingIntent.getActivityAsUser(
- context.createContextAsUser(taskUser, /* flags= */ 0),
+ context.createContextAsUser(userToLaunchWith, /* flags= */ 0),
/* requestCode= */ 0,
launchHomeIntent,
FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
options.toBundle(),
- taskUser,
+ userToLaunchWith,
)
val wct = WindowContainerTransaction()
// The app that is being dragged into desktop mode might cause new transitions, make this
@@ -881,6 +885,7 @@ constructor(
context: Context,
transitions: Transitions,
taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ desktopUserRepositories: DesktopUserRepositories,
interactionJankMonitor: InteractionJankMonitor,
transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
SurfaceControl.Transaction()
@@ -890,6 +895,7 @@ constructor(
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
interactionJankMonitor,
transactionSupplier,
) {
@@ -917,6 +923,7 @@ constructor(
context: Context,
transitions: Transitions,
taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ desktopUserRepositories: DesktopUserRepositories,
interactionJankMonitor: InteractionJankMonitor,
transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
SurfaceControl.Transaction()
@@ -926,6 +933,7 @@ constructor(
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
interactionJankMonitor,
transactionSupplier,
) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
index b4cf8905d02e..88ac865c24b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.hardware.HardwareBuffer;
import android.util.TypedValue;
import android.view.SurfaceControl;
@@ -39,7 +40,6 @@ public final class PipAppIconOverlay extends PipContentOverlay {
private final Context mContext;
private final int mAppIconSizePx;
- private final Rect mAppBounds;
private final int mOverlayHalfSize;
private final Matrix mTmpTransform = new Matrix();
private final float[] mTmpFloat9 = new float[9];
@@ -56,10 +56,6 @@ public final class PipAppIconOverlay extends PipContentOverlay {
final int overlaySize = getOverlaySize(appBounds, destinationBounds);
mOverlayHalfSize = overlaySize >> 1;
- // When the activity is in the secondary split, make sure the scaling center is not
- // offset.
- mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
-
mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
prepareAppIconOverlay(appIcon);
mLeash = new SurfaceControl.Builder()
@@ -85,12 +81,17 @@ public final class PipAppIconOverlay extends PipContentOverlay {
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+ final HardwareBuffer buffer = mBitmap.getHardwareBuffer();
tx.show(mLeash);
tx.setLayer(mLeash, Integer.MAX_VALUE);
- tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+ tx.setBuffer(mLeash, buffer);
tx.setAlpha(mLeash, 0f);
tx.reparent(mLeash, parentLeash);
tx.apply();
+ // Cleanup the bitmap and buffer after setting up the leash
+ mBitmap.recycle();
+ mBitmap = null;
+ buffer.close();
}
@Override
@@ -108,16 +109,6 @@ public final class PipAppIconOverlay extends PipContentOverlay {
.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
-
-
- @Override
- public void detach(SurfaceControl.Transaction tx) {
- super.detach(tx);
- if (mBitmap != null && !mBitmap.isRecycled()) {
- mBitmap.recycle();
- }
- }
-
private void prepareAppIconOverlay(Drawable appIcon) {
final Canvas canvas = new Canvas();
canvas.setBitmap(mBitmap);
@@ -139,6 +130,8 @@ public final class PipAppIconOverlay extends PipContentOverlay {
mOverlayHalfSize + mAppIconSizePx / 2);
appIcon.setBounds(appIconBounds);
appIcon.draw(canvas);
+ Bitmap oldBitmap = mBitmap;
mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+ oldBitmap.recycle();
}
}
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 bb9b479524e5..a57b4b948b42 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
@@ -72,7 +72,6 @@ import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -422,7 +421,7 @@ public class PipTransition extends PipTransitionController implements
final Rect destinationBounds = pipChange.getEndAbsBounds();
final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
if (swipePipToHomeOverlay != null) {
- final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
+ final int overlaySize = PipAppIconOverlay.getOverlaySize(
mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
// It is possible we reparent the PIP activity to a new PIP task (in multi-activity
// apps), so we should also reparent the overlay to the final PIP task.
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 15ac03ccaf30..a799b7f2580e 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
@@ -2859,14 +2859,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED);
mSplitTransitions.setDismissTransition(transition, dismissTop,
EXIT_REASON_APP_FINISHED);
- } else if (isOpening && !mPausingTasks.isEmpty()) {
- // One of the splitting task is opening while animating the split pair in
- // recents, which means to dismiss the split pair to this task.
- int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
- ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
- prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED);
- mSplitTransitions.setDismissTransition(transition, dismissTop,
- EXIT_REASON_APP_FINISHED);
} else if (!isSplitScreenVisible() && isOpening) {
// If split is running in the background and the trigger task is appearing into
// split, prepare to enter split screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index c92e67f1a0c0..a17bcb39f1a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -35,10 +35,8 @@ import android.view.SurfaceControl
import android.view.View
import android.view.WindowInsets.Type.systemBars
import android.view.WindowManager
-import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
-import android.widget.TextView
import android.window.DesktopModeFlags
import android.window.SurfaceSyncGroup
import androidx.annotation.StringRes
@@ -540,17 +538,35 @@ class HandleMenu(
return@setOnTouchListener true
}
- with(context.resources) {
- // Update a11y read out to say "double tap to enter desktop windowing mode"
+ with(context) {
+ // Update a11y announcement out to say "double tap to enter Fullscreen"
+ ViewCompat.replaceAccessibilityAction(
+ fullscreenBtn, ACTION_CLICK,
+ getString(
+ R.string.app_handle_menu_accessibility_announce,
+ getString(R.string.fullscreen_text)
+ ),
+ null,
+ )
+
+ // Update a11y announcement out to say "double tap to enter Desktop View"
ViewCompat.replaceAccessibilityAction(
desktopBtn, ACTION_CLICK,
- getString(R.string.app_handle_menu_talkback_desktop_mode_button_text), null
+ getString(
+ R.string.app_handle_menu_accessibility_announce,
+ getString(R.string.desktop_text)
+ ),
+ null,
)
- // Update a11y read out to say "double tap to enter split screen mode"
+ // Update a11y announcement to say "double tap to enter Split Screen"
ViewCompat.replaceAccessibilityAction(
splitscreenBtn, ACTION_CLICK,
- getString(R.string.app_handle_menu_talkback_split_screen_mode_button_text), null
+ getString(
+ R.string.app_handle_menu_accessibility_announce,
+ getString(R.string.split_screen_text)
+ ),
+ null,
)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 4762bc21d79c..90c865e502fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -93,9 +93,6 @@ class AppHeaderViewHolder(
private val lightColors = dynamicLightColorScheme(context)
private val darkColors = dynamicDarkColorScheme(context)
- private val headerButtonOpenMenuA11yText = context.resources
- .getString(R.string.desktop_mode_app_header_chip_text)
-
/**
* The corner radius to apply to the app chip, maximize and close button's background drawable.
**/
@@ -231,35 +228,29 @@ class AppHeaderViewHolder(
}
}
- val a11yActionOpenHeaderMenu = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
- headerButtonOpenMenuA11yText)
- openMenuButton.accessibilityDelegate = object : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(a11yActionOpenHeaderMenu)
- }
- }
+ // Update a11y announcement to say "double tap to open menu"
+ ViewCompat.replaceAccessibilityAction(
+ openMenuButton,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.app_handle_chip_accessibility_announce),
+ null
+ )
- with(context.resources) {
- // Update a11y read out to say "double tap to maximize or restore window size"
- ViewCompat.replaceAccessibilityAction(
- maximizeWindowButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.maximize_button_talkback_action_maximize_restore_text),
- null
- )
+ // Update a11y announcement to say "double tap to maximize or restore window size"
+ ViewCompat.replaceAccessibilityAction(
+ maximizeWindowButton,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.maximize_button_talkback_action_maximize_restore_text),
+ null
+ )
- // Update a11y read out to say "double tap to minimize app window"
- ViewCompat.replaceAccessibilityAction(
- minimizeWindowButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.minimize_button_talkback_action_maximize_restore_text),
- null
- )
- }
+ // Update a11y announcement out to say "double tap to minimize app window"
+ ViewCompat.replaceAccessibilityAction(
+ minimizeWindowButton,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.minimize_button_talkback_action_maximize_restore_text),
+ null
+ )
}
override fun bindData(data: HeaderData) {
@@ -275,7 +266,8 @@ class AppHeaderViewHolder(
/** Sets the app's name in the header. */
fun setAppName(name: CharSequence) {
appNameTextView.text = name
- openMenuButton.contentDescription = name
+ openMenuButton.contentDescription =
+ context.getString(R.string.desktop_mode_app_header_chip_text, name)
}
/** Sets the app's icon in the header. */
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 02b2cec8dbdb..ae73dae99d6f 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -53,10 +53,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 98c5bc960c35..718bf322f6a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -171,7 +171,6 @@ import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
@@ -363,9 +362,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
shellInit.init()
- val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+ val captor = argumentCaptor<RecentsTransitionStateListener>()
verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
- recentsTransitionStateListener = captor.value
+ recentsTransitionStateListener = captor.firstValue
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
@@ -441,7 +440,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
val task1 = setUpFreeformTask()
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ val argumentCaptor = argumentCaptor<Boolean>()
controller.toggleDesktopTaskSize(
task1,
ToggleTaskSizeInteraction(
@@ -461,7 +460,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
STABLE_BOUNDS.height(),
displayController,
)
- assertThat(argumentCaptor.value).isTrue()
+ assertThat(argumentCaptor.firstValue).isTrue()
}
@Test
@@ -476,7 +475,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ val argumentCaptor = argumentCaptor<Boolean>()
controller.toggleDesktopTaskSize(
task1,
ToggleTaskSizeInteraction(
@@ -497,7 +496,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(displayController),
anyOrNull(),
)
- assertThat(argumentCaptor.value).isFalse()
+ assertThat(argumentCaptor.firstValue).isFalse()
}
@Test
@@ -547,6 +546,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -581,7 +581,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wct =
getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
// Wallpaper is moved to front.
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 0, wallpaperToken)
// Desk is activated.
verify(desksOrganizer).activateDesk(wct, deskId)
}
@@ -783,6 +783,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -825,7 +826,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -842,6 +844,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
wct.assertReorderAt(index = 2, task2)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
@Test
@DisableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
@@ -860,9 +880,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
- whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
- .thenReturn(Binder())
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -871,10 +891,18 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
- )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
@@ -899,6 +927,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
.thenReturn(Binder())
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -991,6 +1020,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
/** TODO: b/362720497 - add multi-desk version when minimization is implemented. */
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -1569,6 +1599,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1736,7 +1767,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
@@ -1751,12 +1782,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(desktopModeEnterExitTransitionListener)
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
@@ -1768,7 +1799,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(desktopModeEnterExitTransitionListener)
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
@@ -1802,6 +1833,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
@@ -1828,6 +1860,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_multiDesksEnabled() {
val freeformTask = setUpFreeformTask()
@@ -1840,7 +1873,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
val wct = getLatestEnterDesktopWct()
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 0, wallpaperToken)
verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, fullscreenTask)
verify(desksOrganizer).activateDesk(wct, deskId = 0)
verify(desktopModeEnterExitTransitionListener)
@@ -1967,6 +2000,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
@@ -2224,26 +2258,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
setUpHomeTask()
val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
setUpHomeTask()
val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
- assertThat(transitionHandlerArgCaptor.value)
+ assertThat(transitionHandlerArgCaptor.firstValue)
.isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
}
@@ -2718,9 +2752,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2759,9 +2793,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2775,9 +2809,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
+ captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
}
@Test
@@ -2791,10 +2825,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// The only active task is being minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -2808,9 +2842,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// The only active task is already minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2825,9 +2859,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2845,10 +2879,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// task1 is the only visible task as task2 is minimized.
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
// Adds remove wallpaper operation
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -2987,6 +3021,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -3118,6 +3153,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -3152,7 +3188,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createFreeformTask()
+
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
@@ -3180,6 +3218,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -4635,7 +4674,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctArgument = argumentCaptor<WindowContainerTransaction>()
verify(splitScreenController)
.requestEnterSplitSelect(
eq(task2),
@@ -4643,9 +4682,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
eq(task2.configuration.windowConfiguration.bounds),
)
- assertThat(wctArgument.value.hierarchyOps).hasSize(1)
+ assertThat(wctArgument.firstValue.hierarchyOps).hasSize(1)
// Removes wallpaper activity when leaving desktop
- wctArgument.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ wctArgument.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -4660,7 +4699,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctArgument = argumentCaptor<WindowContainerTransaction>()
verify(splitScreenController)
.requestEnterSplitSelect(
eq(task2),
@@ -4669,7 +4708,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(task2.configuration.windowConfiguration.bounds),
)
// Does not remove wallpaper activity, as desktop still has visible desktop tasks
- assertThat(wctArgument.value.hierarchyOps).isEmpty()
+ assertThat(wctArgument.firstValue.hierarchyOps).isEmpty()
}
@Test
@@ -4677,7 +4716,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun newWindow_fromFullscreenOpensInSplit() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(
@@ -4690,7 +4729,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(true),
eq(SPLIT_INDEX_UNDEFINED),
)
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4699,7 +4738,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun newWindow_fromSplitOpensInSplit() {
setUpLandscapeDisplay()
val task = setUpSplitScreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(
@@ -4712,7 +4751,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(true),
eq(SPLIT_INDEX_UNDEFINED),
)
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4807,11 +4846,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
setUpLandscapeDisplay()
val task = setUpFullscreenTask()
val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
.startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4821,11 +4860,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
setUpLandscapeDisplay()
val task = setUpSplitScreenTask()
val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
.startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -5912,35 +5951,37 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
mockDragEvent,
mockCallback as Consumer<Boolean>,
)
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
var expectedWindowingMode: Int
if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
// Fullscreen launches currently use default transitions
- verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+ verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
} else {
expectedWindowingMode = WINDOWING_MODE_FREEFORM
if (tabTearingAnimationFlagEnabled) {
verify(desktopMixedTransitionHandler)
.startLaunchTransition(
eq(TRANSIT_OPEN),
- capture(arg),
+ arg.capture(),
anyOrNull(),
anyOrNull(),
anyOrNull(),
)
} else {
// All other launches use a special handler.
- verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ verify(dragAndDropTransitionHandler).handleDropEvent(arg.capture())
}
}
assertThat(
- ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
.launchWindowingMode
)
.isEqualTo(expectedWindowingMode)
- assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions).launchBounds)
+ assertThat(
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
+ .launchBounds
+ )
.isEqualTo(expectedBounds)
}
@@ -6122,52 +6163,49 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@WindowManager.TransitionType type: Int = TRANSIT_OPEN,
handlerClass: Class<out TransitionHandler>? = null,
): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
if (handlerClass == null) {
verify(transitions).startTransition(eq(type), arg.capture(), isNull())
} else {
verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
}
- return arg.value
+ return arg.lastValue
}
private fun getLatestToggleResizeDesktopTaskWct(
currentBounds: Rect? = null
): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
- .startTransition(capture(arg), eq(currentBounds))
- return arg.value
+ .startTransition(arg.capture(), eq(currentBounds))
+ return arg.lastValue
}
private fun getLatestDesktopMixedTaskWct(
@WindowManager.TransitionType type: Int = TRANSIT_OPEN
): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(desktopMixedTransitionHandler)
- .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull())
- return arg.value
+ .startLaunchTransition(eq(type), arg.capture(), anyOrNull(), anyOrNull(), anyOrNull())
+ return arg.lastValue
}
private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
- return arg.value
+ return arg.lastValue
}
private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
- return arg.value
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(arg.capture())
+ return arg.lastValue
}
private fun getLatestExitDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
- return arg.value
+ return arg.lastValue
}
private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index 83e48728c4f2..030bb1ace49d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -123,8 +123,26 @@ class DesktopUserRepositoriesTest : ShellTestCase() {
assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2)
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun getUserForProfile_flagEnabled_returnsUserIdForProfile() {
+ userRepositories.onUserChanged(USER_ID_2, mock())
+ val profiles: MutableList<UserInfo> =
+ mutableListOf(
+ UserInfo(USER_ID_2, "User profile", 0),
+ UserInfo(PROFILE_ID_1, "Work profile", 0),
+ )
+ userRepositories.onUserProfilesChanged(profiles)
+
+ val userIdForProfile = userRepositories.getUserIdForProfile(PROFILE_ID_1)
+
+ assertThat(userIdForProfile).isEqualTo(USER_ID_2)
+ }
+
private companion object {
const val USER_ID_1 = 7
+ const val USER_ID_2 = 8
+ const val PROFILE_ID_1 = 4
const val PROFILE_ID_2 = 5
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 25246d9984c3..1732875f1d57 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -70,6 +70,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var draggedTaskLeash: SurfaceControl
@Mock private lateinit var homeTaskLeash: SurfaceControl
+ @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -84,6 +85,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
mockInteractionJankMonitor,
transactionSupplier,
)
@@ -93,6 +95,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
mockInteractionJankMonitor,
transactionSupplier,
)
@@ -484,17 +487,22 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
- val startTransition = startDrag(
- springHandler, task, finishTransaction = playingFinishTransaction, homeChange = null)
+ val startTransition =
+ startDrag(
+ springHandler,
+ task,
+ finishTransaction = playingFinishTransaction,
+ homeChange = null,
+ )
springHandler.onTaskResizeAnimationListener = mock()
springHandler.mergeAnimation(
transition = mock<IBinder>(),
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task,
- ),
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
startT = mergedStartTransaction,
finishT = mergedFinishTransaction,
mergeTarget = startTransition,
@@ -723,7 +731,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private fun createTransitionInfo(
type: Int,
draggedTask: RunningTaskInfo,
- homeChange: TransitionInfo.Change? = createHomeChange()) =
+ homeChange: TransitionInfo.Change? = createHomeChange(),
+ ) =
TransitionInfo(type, /* flags= */ 0).apply {
homeChange?.let { addChange(it) }
addChange( // Dragged Task.
@@ -741,11 +750,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
}
- private fun createHomeChange() = TransitionInfo.Change(mock(), homeTaskLeash).apply {
- parent = null
- taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- flags = flags or FLAG_IS_WALLPAPER
- }
+ private fun createHomeChange() =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ flags = flags or FLAG_IS_WALLPAPER
+ }
private fun systemPropertiesKey(name: String) =
"${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
index 7cd46af9402b..fd22a84dee5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
import android.graphics.Insets
import android.graphics.Rect
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.wm.shell.shared.bubbles.DragZoneFactory.DesktopWindowModeChecker
@@ -34,6 +36,7 @@ private typealias DragZoneVerifier = (dragZone: DragZone) -> Unit
/** Unit tests for [DragZoneFactory]. */
class DragZoneFactoryTest {
+ private val context = getApplicationContext<Context>()
private lateinit var dragZoneFactory: DragZoneFactory
private val tabletPortrait =
DeviceConfig(
@@ -57,7 +60,12 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForBubbleBar_tablet() {
dragZoneFactory =
- DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ DragZoneFactory(
+ context,
+ tabletPortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.BubbleBar(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -73,7 +81,12 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForBubble_tablet_portrait() {
dragZoneFactory =
- DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ DragZoneFactory(
+ context,
+ tabletPortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -92,7 +105,13 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForBubble_tablet_landscape() {
- dragZoneFactory = DragZoneFactory(tabletLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ tabletLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -111,7 +130,13 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForBubble_foldable_portrait() {
- dragZoneFactory = DragZoneFactory(foldablePortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldablePortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -129,7 +154,13 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForBubble_foldable_landscape() {
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -148,7 +179,12 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForExpandedView_tablet_portrait() {
dragZoneFactory =
- DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ DragZoneFactory(
+ context,
+ tabletPortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(
DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
@@ -169,9 +205,17 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForExpandedView_tablet_landscape() {
- dragZoneFactory = DragZoneFactory(tabletLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ tabletLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
val expectedZones: List<DragZoneVerifier> =
listOf(
verifyInstance<DragZone.Dismiss>(),
@@ -188,9 +232,17 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForExpandedView_foldable_portrait() {
- dragZoneFactory = DragZoneFactory(foldablePortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldablePortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
val expectedZones: List<DragZoneVerifier> =
listOf(
verifyInstance<DragZone.Dismiss>(),
@@ -206,9 +258,17 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForExpandedView_foldable_landscape() {
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
val expectedZones: List<DragZoneVerifier> =
listOf(
verifyInstance<DragZone.Dismiss>(),
@@ -225,7 +285,13 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForBubble_tablet_desktopModeDisabled() {
isDesktopWindowModeSupported = false
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
@@ -234,9 +300,17 @@ class DragZoneFactoryTest {
@Test
fun dragZonesForExpandedView_tablet_desktopModeDisabled() {
isDesktopWindowModeSupported = false
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 55e9de5eff5f..ae1e4e0fbbc1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -181,6 +181,17 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+ @DisableCompatChanges(ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ @EnableCompatChanges(ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS)
+ fun testShouldExcludeCaptionFromAppBounds_resizeable_overridden_true() {
+ assertTrue(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(
+ setUpFreeformTask().apply { isResizeable = true })
+ )
+ }
+
fun setUpFreeformTask(): TaskInfo =
createFreeformTask().apply {
val componentName =
diff --git a/media/java/android/media/audiofx/HapticGenerator.java b/media/java/android/media/audiofx/HapticGenerator.java
index d2523ef43b9e..7f94ddea9b84 100644
--- a/media/java/android/media/audiofx/HapticGenerator.java
+++ b/media/java/android/media/audiofx/HapticGenerator.java
@@ -36,6 +36,20 @@ import java.util.UUID;
* <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
* effects.
+ *
+ * <pre>{@code
+ * AudioManager audioManager = context.getSystemService(AudioManager.class);
+ * player = MediaPlayer.create(
+ * context,
+ * audioUri,
+ * new AudioAttributes.Builder().setHapticChannelsMuted(false).build(),
+ * audioManager.generateAudioSessionId()
+ * );
+ * if (HapticGenerator.isAvailable()) {
+ * HapticGenerator.create(player.getAudioSessionId()).setEnabled(true);
+ * }
+ * player.start();
+ * }</pre>
*/
public class HapticGenerator extends AudioEffect implements AutoCloseable {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm
new file mode 100644
index 000000000000..0059d0040be4
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm
@@ -0,0 +1,400 @@
+# Copyright 2025 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.
+
+#
+# English (India) keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '`'
+ base: '`'
+ shift: '~'
+ ralt: '\u0300'
+ ralt+shift: '\u0303'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '@'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+ ralt, ctrl+shift: '\u20b9'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '^'
+ ralt+shift: '\u0302'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '&'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '*'
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: '('
+ ralt+shift: '\u0306'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: ')'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+ ralt+shift: '\u0331'
+}
+
+key EQUALS {
+ label: '='
+ base: '='
+ shift: '+'
+ ralt: '\u2013'
+ ralt+shift: '\u2014'
+}
+
+### ROW 2
+
+key Q {
+ label: 'Q'
+ base: 'q'
+ shift, capslock: 'Q'
+ capslock+shift: 'q'
+ ralt: '\u00e6'
+ ralt+shift, ralt+capslock: '\u00c6'
+ ralt+shift+capslock: '\u00e6'
+}
+
+key W {
+ label: 'W'
+ base: 'w'
+ shift, capslock: 'W'
+ capslock+shift: 'w'
+}
+
+key E {
+ label: 'E'
+ base: 'e'
+ shift, capslock: 'E'
+ capslock+shift: 'e'
+ ralt: '\u0113'
+ ralt+shift, ralt+capslock: '\u0112'
+ ralt+shift+capslock: '\u0113'
+}
+
+key R {
+ label: 'R'
+ base: 'r'
+ shift, capslock: 'R'
+ capslock+shift: 'r'
+}
+
+key T {
+ label: 'T'
+ base: 't'
+ shift, capslock: 'T'
+ capslock+shift: 't'
+ ralt: '\u1e6d'
+ ralt+shift, ralt+capslock: '\u1e6c'
+ ralt+shift+capslock: '\u1e6d'
+}
+
+key Y {
+ label: 'Y'
+ base: 'y'
+ shift, capslock: 'Y'
+ capslock+shift: 'y'
+ ralt: '\u00f1'
+ ralt+shift, ralt+capslock: '\u00d1'
+ ralt+shift+capslock: '\u00f1'
+}
+
+key U {
+ label: 'U'
+ base: 'u'
+ shift, capslock: 'U'
+ capslock+shift: 'u'
+ ralt: '\u016b'
+ ralt+shift, ralt+capslock: '\u016a'
+ ralt+shift+capslock: '\u016b'
+}
+
+key I {
+ label: 'I'
+ base: 'i'
+ shift, capslock: 'I'
+ capslock+shift: 'i'
+ ralt: '\u012b'
+ ralt+shift, ralt+capslock: '\u012a'
+ ralt+shift+capslock: '\u012b'
+}
+
+key O {
+ label: 'O'
+ base: 'o'
+ shift, capslock: 'O'
+ capslock+shift: 'o'
+ ralt: '\u014d'
+ ralt+shift, ralt+capslock: '\u014c'
+ ralt+shift+capslock: '\u014d'
+}
+
+key P {
+ label: 'P'
+ base: 'p'
+ shift, capslock: 'P'
+ capslock+shift: 'p'
+}
+
+key LEFT_BRACKET {
+ label: '['
+ base: '['
+ shift: '{'
+}
+
+key RIGHT_BRACKET {
+ label: ']'
+ base: ']'
+ shift: '}'
+}
+
+### ROW 3
+
+key A {
+ label: 'A'
+ base: 'a'
+ shift, capslock: 'A'
+ capslock+shift: 'a'
+ ralt: '\u0101'
+ ralt+shift, ralt+capslock: '\u0100'
+ ralt+shift+capslock: '\u0101'
+}
+
+key S {
+ label: 'S'
+ base: 's'
+ shift, capslock: 'S'
+ capslock+shift: 's'
+ ralt: '\u015b'
+ ralt+shift, ralt+capslock: '\u015a'
+ ralt+shift+capslock: '\u015b'
+}
+
+key D {
+ label: 'D'
+ base: 'd'
+ shift, capslock: 'D'
+ capslock+shift: 'd'
+ ralt: '\u1e0d'
+ ralt+shift, ralt+capslock: '\u1e0c'
+ ralt+shift+capslock: '\u1e0d'
+}
+
+key F {
+ label: 'F'
+ base: 'f'
+ shift, capslock: 'F'
+ capslock+shift: 'f'
+}
+
+key G {
+ label: 'G'
+ base: 'g'
+ shift, capslock: 'G'
+ capslock+shift: 'g'
+ ralt: '\u1e45'
+ ralt+shift, ralt+capslock: '\u1e44'
+ ralt+shift+capslock: '\u1e45'
+}
+
+key H {
+ label: 'H'
+ base: 'h'
+ shift, capslock: 'H'
+ capslock+shift: 'h'
+ ralt: '\u1e25'
+ ralt+shift, ralt+capslock: '\u1e24'
+ ralt+shift+capslock: '\u1e25'
+}
+
+key J {
+ label: 'J'
+ base: 'j'
+ shift, capslock: 'J'
+ capslock+shift: 'j'
+}
+
+key K {
+ label: 'K'
+ base: 'k'
+ shift, capslock: 'K'
+ capslock+shift: 'k'
+}
+
+key L {
+ label: 'L'
+ base: 'l'
+ shift, capslock: 'L'
+ capslock+shift: 'l'
+}
+
+key SEMICOLON {
+ label: ';'
+ base: ';'
+ shift: ':'
+}
+
+key APOSTROPHE {
+ label: '\''
+ base: '\''
+ shift: '\u0022'
+ ralt: '\u030d'
+ ralt+shift: '\u030e'
+}
+
+key BACKSLASH {
+ label: '\\'
+ base: '\\'
+ shift: '|'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '\\'
+ base: '\\'
+ shift: '|'
+}
+
+key Z {
+ label: 'Z'
+ base: 'z'
+ shift, capslock: 'Z'
+ capslock+shift: 'z'
+}
+
+key X {
+ label: 'X'
+ base: 'x'
+ shift, capslock: 'X'
+ capslock+shift: 'x'
+ ralt: '\u1e63'
+ ralt+shift, ralt+capslock: '\u1e62'
+ ralt+shift+capslock: '\u1e63'
+}
+
+key C {
+ label: 'C'
+ base: 'c'
+ shift, capslock: 'C'
+ capslock+shift: 'c'
+}
+
+key V {
+ label: 'V'
+ base: 'v'
+ shift, capslock: 'V'
+ capslock+shift: 'v'
+}
+
+key B {
+ label: 'B'
+ base: 'b'
+ shift, capslock: 'B'
+ capslock+shift: 'b'
+}
+
+key N {
+ label: 'N'
+ base: 'n'
+ shift, capslock: 'N'
+ capslock+shift: 'n'
+ ralt: '\u1e47'
+ ralt+shift, ralt+capslock: '\u1e46'
+ ralt+shift+capslock: '\u1e47'
+}
+
+key M {
+ label: 'M'
+ base: 'm'
+ shift, capslock: 'M'
+ capslock+shift: 'm'
+ ralt: '\u1e41'
+ ralt+shift, ralt+capslock: '\u1e40'
+ ralt+shift+capslock: '\u1e41'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: '<'
+ ralt+shift: '\u030C'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: '>'
+ ralt: '\u0323'
+}
+
+key SLASH {
+ label: '/'
+ base: '/'
+ shift: '?'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index bd7cdc481524..8a397a5e9d18 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -167,4 +167,7 @@
<!-- Romanian keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_romanian">Romanian</string>
+
+ <!-- English (India) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_english_india">English (India)</string>
</resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 9ce9a87a1f9f..fa0ed13fb32c 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -367,4 +367,11 @@
android:keyboardLayout="@raw/keyboard_layout_romanian"
android:keyboardLocale="ro-Latn-RO"
android:keyboardLayoutType="qwerty" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_english_india"
+ android:label="@string/keyboard_layout_english_india"
+ android:keyboardLayout="@raw/keyboard_layout_english_india"
+ android:keyboardLocale="en-Latn-IN"
+ android:keyboardLayoutType="qwerty" />
</keyboard-layouts>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index bf86911ee683..572444edea29 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -30,11 +30,13 @@ import android.util.Log;
import androidx.annotation.ChecksSdkIntAtLeast;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.flags.Flags;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -385,7 +387,7 @@ public class CsipDeviceManager {
preferredMainDevice.refresh();
hasChanged = true;
}
- syncAudioSharingSourceIfNeeded(preferredMainDevice);
+ syncAudioSharingStatusIfNeeded(preferredMainDevice);
}
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -399,13 +401,16 @@ public class CsipDeviceManager {
return userManager != null && userManager.isManagedProfile();
}
- private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
+ private void syncAudioSharingStatusIfNeeded(CachedBluetoothDevice mainDevice) {
boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
- if (isAudioSharingEnabled) {
+ if (isAudioSharingEnabled && mainDevice != null) {
if (isWorkProfile()) {
- log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+ log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, work profile");
return;
}
+ Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
+ deviceSet.add(mainDevice);
+ deviceSet.addAll(mainDevice.getMemberDevice());
boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
&& BluetoothUtils.hasConnectedBroadcastSource(
mainDevice, mBtManager);
@@ -419,9 +424,6 @@ public class CsipDeviceManager {
if (metadata != null && assistant != null) {
log("addMemberDevicesIntoMainDevice: sync audio sharing source after "
+ "combining the top level devices.");
- Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
- deviceSet.add(mainDevice);
- deviceSet.addAll(mainDevice.getMemberDevice());
Set<BluetoothDevice> sinksToSync = deviceSet.stream()
.map(CachedBluetoothDevice::getDevice)
.filter(device ->
@@ -435,8 +437,24 @@ public class CsipDeviceManager {
}
}
}
+ if (Flags.enableTemporaryBondDevicesUi()) {
+ log("addMemberDevicesIntoMainDevice: sync temp bond metadata for audio sharing "
+ + "sinks after combining the top level devices.");
+ Set<BluetoothDevice> sinksToSync = deviceSet.stream()
+ .map(CachedBluetoothDevice::getDevice).filter(Objects::nonNull).collect(
+ Collectors.toSet());
+ if (sinksToSync.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice)) {
+ for (BluetoothDevice device : sinksToSync) {
+ if (!BluetoothUtils.isTemporaryBondDevice(device)) {
+ log("addMemberDevicesIntoMainDevice: sync temp bond metadata for "
+ + device.getAnonymizedAddress());
+ BluetoothUtils.setTemporaryBondMetadata(device);
+ }
+ }
+ }
+ }
} else {
- log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
+ log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, flag disabled");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
index 10156c404ebf..bac564c7d0f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
@@ -20,6 +20,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.media.MediaMetadata
import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
@@ -98,16 +99,22 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
}
/** Set volume `level` to remote media `token` */
- fun setVolume(token: MediaSession.Token, level: Int) {
+ fun setVolume(sessionId: SessionId, volumeLevel: Int) {
+ when (sessionId) {
+ is SessionId.Media -> setMediaSessionVolume(sessionId.token, volumeLevel)
+ }
+ }
+
+ private fun setMediaSessionVolume(token: MediaSession.Token, volumeLevel: Int) {
val record = mRecords[token]
if (record == null) {
Log.w(TAG, "setVolume: No record found for token $token")
return
}
if (D.BUG) {
- Log.d(TAG, "Setting level to $level")
+ Log.d(TAG, "Setting level to $volumeLevel")
}
- record.controller.setVolumeTo(level, 0)
+ record.controller.setVolumeTo(volumeLevel, 0)
}
private fun onRemoteVolumeChangedH(sessionToken: MediaSession.Token, flags: Int) {
@@ -122,7 +129,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
)
}
val token = controller.sessionToken
- mCallbacks.onRemoteVolumeChanged(token, flags)
+ mCallbacks.onRemoteVolumeChanged(SessionId.from(token), flags)
}
private fun onUpdateRemoteSessionListH(sessionToken: MediaSession.Token?) {
@@ -158,7 +165,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
controller.registerCallback(record, mHandler)
}
val record = mRecords[token]
- val remote = isRemote(playbackInfo)
+ val remote = playbackInfo.isRemote()
if (remote) {
updateRemoteH(token, record!!.name, playbackInfo)
record.sentRemote = true
@@ -172,7 +179,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
Log.d(TAG, "Removing " + record.name + " sentRemote=" + record.sentRemote)
}
if (record.sentRemote) {
- mCallbacks.onRemoteRemoved(token)
+ mCallbacks.onRemoteRemoved(SessionId.from(token))
record.sentRemote = false
}
}
@@ -213,8 +220,8 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
private fun updateRemoteH(
token: MediaSession.Token,
name: String?,
- pi: MediaController.PlaybackInfo,
- ) = mCallbacks.onRemoteUpdate(token, name, pi)
+ playbackInfo: PlaybackInfo,
+ ) = mCallbacks.onRemoteUpdate(SessionId.from(token), name, VolumeInfo.from(playbackInfo))
private inner class MediaControllerRecord(val controller: MediaController) :
MediaController.Callback() {
@@ -225,7 +232,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
return method + " " + controller.packageName + " "
}
- override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
+ override fun onAudioInfoChanged(info: PlaybackInfo) {
if (D.BUG) {
Log.d(
TAG,
@@ -235,9 +242,9 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
sentRemote),
)
}
- val remote = isRemote(info)
+ val remote = info.isRemote()
if (!remote && sentRemote) {
- mCallbacks.onRemoteRemoved(controller.sessionToken)
+ mCallbacks.onRemoteRemoved(SessionId.from(controller.sessionToken))
sentRemote = false
} else if (remote) {
updateRemoteH(controller.sessionToken, name, info)
@@ -301,20 +308,36 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
}
}
+ /** Opaque id for ongoing sessions that support volume adjustment. */
+ sealed interface SessionId {
+
+ companion object {
+ fun from(token: MediaSession.Token) = Media(token)
+ }
+
+ data class Media(val token: MediaSession.Token) : SessionId
+ }
+
+ /** Holds session volume information. */
+ data class VolumeInfo(val currentVolume: Int, val maxVolume: Int) {
+
+ companion object {
+
+ fun from(playbackInfo: PlaybackInfo) =
+ VolumeInfo(playbackInfo.currentVolume, playbackInfo.maxVolume)
+ }
+ }
+
/** Callback for remote media sessions */
interface Callbacks {
/** Invoked when remote media session is updated */
- fun onRemoteUpdate(
- token: MediaSession.Token?,
- name: String?,
- pi: MediaController.PlaybackInfo?,
- )
+ fun onRemoteUpdate(token: SessionId?, name: String?, volumeInfo: VolumeInfo?)
/** Invoked when remote media session is removed */
- fun onRemoteRemoved(token: MediaSession.Token?)
+ fun onRemoteRemoved(token: SessionId?)
/** Invoked when remote volume is changed */
- fun onRemoteVolumeChanged(token: MediaSession.Token?, flags: Int)
+ fun onRemoteVolumeChanged(token: SessionId?, flags: Int)
}
companion object {
@@ -325,12 +348,11 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
const val UPDATE_REMOTE_SESSION_LIST: Int = 3
private const val USE_SERVICE_LABEL = false
-
- private fun isRemote(pi: MediaController.PlaybackInfo?): Boolean =
- pi != null && pi.playbackType == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
}
}
+private fun PlaybackInfo?.isRemote() = this?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+
private fun MediaController.dump(n: Int, writer: PrintWriter) {
writer.println(" Controller $n: $packageName")
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index fd14d1ff6786..2eccaa626f3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -40,6 +40,8 @@ import android.content.Context;
import android.os.Looper;
import android.os.Parcel;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.flags.Flags;
@@ -74,6 +76,9 @@ public class CsipDeviceManagerTest {
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
+ private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+ private static final String TEMP_BOND_METADATA =
+ "<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private final static int GROUP1 = 1;
private final BluetoothClass DEVICE_CLASS_1 =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
@@ -337,6 +342,7 @@ public class CsipDeviceManagerTest {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
@@ -346,7 +352,6 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -359,6 +364,7 @@ public class CsipDeviceManagerTest {
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void
addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_workProfile_doNothing() {
// Condition: The preferredDevice is main and there is another main device in top list
@@ -369,7 +375,6 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -377,6 +382,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mUserManager.isManagedProfile()).thenReturn(true);
@@ -387,10 +394,13 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant, never()).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ verify(mDevice1, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncState() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice1;
@@ -399,7 +409,6 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -407,6 +416,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -415,6 +426,8 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ verify(mDevice1).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
@@ -436,13 +449,13 @@ public class CsipDeviceManagerTest {
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(false);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
@@ -457,16 +470,20 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
any(BluetoothLeBroadcastMetadata.class), anyBoolean());
+ verify(mDevice2, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ verify(mDevice3, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncSource() {
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncState() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -474,6 +491,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
+ when(mDevice1.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -488,6 +507,10 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant).addSource(mDevice2, metadata, /* isGroupOp= */ false);
verify(mAssistant).addSource(mDevice3, metadata, /* isGroupOp= */ false);
+ verify(mDevice2).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ verify(mDevice3).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 246aa7158cab..85617bad1a91 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -809,7 +809,9 @@ public class SettingsBackupTest {
Settings.Secure.DND_CONFIGS_MIGRATED,
Settings.Secure.NAVIGATION_MODE_RESTORE,
Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
- Settings.Secure.V_TO_U_RESTORE_DENYLIST);
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+ Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
+ Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
index 93690d48cd04..b0fd925daec3 100644
--- a/packages/Shell/src/com/android/shell/BugreportPrefs.java
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -23,25 +23,24 @@ import android.content.SharedPreferences;
* Preferences related to bug reports.
*/
final class BugreportPrefs {
- static final String PREFS_BUGREPORT = "bugreports";
-
- private static final String KEY_WARNING_STATE = "warning-state";
-
- static final int STATE_UNKNOWN = 0;
- // Shows the warning dialog.
- static final int STATE_SHOW = 1;
- // Skips the warning dialog.
- static final int STATE_HIDE = 2;
static int getWarningState(Context context, int def) {
- final SharedPreferences prefs = context.getSharedPreferences(
- PREFS_BUGREPORT, Context.MODE_PRIVATE);
- return prefs.getInt(KEY_WARNING_STATE, def);
+ String prefsBugreport = context.getResources().getString(
+ com.android.internal.R.string.prefs_bugreport);
+ String keyWarningState = context.getResources().getString(
+ com.android.internal.R.string.key_warning_state);
+ final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+ Context.MODE_PRIVATE);
+ return prefs.getInt(keyWarningState, def);
}
static void setWarningState(Context context, int value) {
- final SharedPreferences prefs = context.getSharedPreferences(
- PREFS_BUGREPORT, Context.MODE_PRIVATE);
- prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+ String prefsBugreport = context.getResources().getString(
+ com.android.internal.R.string.prefs_bugreport);
+ String keyWarningState = context.getResources().getString(
+ com.android.internal.R.string.key_warning_state);
+ final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+ Context.MODE_PRIVATE);
+ prefs.edit().putInt(keyWarningState, value).apply();
}
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 61f49db07abc..fb0678fedb56 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,8 +21,6 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.flags.Flags.handleBugreportsForWear;
@@ -1347,7 +1345,11 @@ public class BugreportProgressService extends Service {
}
private boolean hasUserDecidedNotToGetWarningMessage() {
- return getWarningState(mContext, STATE_UNKNOWN) == STATE_HIDE;
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ return getWarningState(mContext, bugreportStateUnknown) == bugreportStateHide;
}
private void maybeShowWarningMessageAndCloseNotification(int id) {
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
index a44e23603f52..0e835f91aca6 100644
--- a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -16,9 +16,6 @@
package com.android.shell;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.BugreportPrefs.setWarningState;
import static com.android.shell.BugreportProgressService.sendShareIntent;
@@ -69,12 +66,19 @@ public class BugreportWarningActivity extends AlertActivity
mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
- final int state = getWarningState(this, STATE_UNKNOWN);
+ int bugreportStateUnknown = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ int bugreportStateShow = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
+
+ final int state = getWarningState(this, bugreportStateUnknown);
final boolean checked;
if (Build.IS_USER) {
- checked = state == STATE_HIDE; // Only checks if specifically set to.
+ checked = state == bugreportStateHide; // Only checks if specifically set to.
} else {
- checked = state != STATE_SHOW; // Checks by default.
+ checked = state != bugreportStateShow; // Checks by default.
}
mConfirmRepeat.setChecked(checked);
@@ -83,9 +87,14 @@ public class BugreportWarningActivity extends AlertActivity
@Override
public void onClick(DialogInterface dialog, int which) {
+ int bugreportStateHide = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ int bugreportStateShow = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
if (which == AlertDialog.BUTTON_POSITIVE) {
// Remember confirm state, and launch target
- setWarningState(this, mConfirmRepeat.isChecked() ? STATE_HIDE : STATE_SHOW);
+ setWarningState(this, mConfirmRepeat.isChecked() ? bugreportStateHide
+ : bugreportStateShow);
if (mSendIntent != null) {
sendShareIntent(this, mSendIntent);
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 7bda2ea790b0..2d6abe6cdc93 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,10 +19,6 @@ package com.android.shell;
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
-import static com.android.shell.BugreportPrefs.PREFS_BUGREPORT;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.BugreportPrefs.setWarningState;
import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_REQUESTED;
@@ -201,8 +197,9 @@ public class BugreportReceiverTest {
return null;
}).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
any(), anyBoolean(), anyBoolean());
-
- setWarningState(mContext, STATE_HIDE);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ setWarningState(mContext, bugreportStateHide);
mUiBot.turnScreenOn();
}
@@ -469,22 +466,31 @@ public class BugreportReceiverTest {
@Test
public void testBugreportFinished_withWarningUnknownState() throws Exception {
- bugreportFinishedWithWarningTest(STATE_UNKNOWN);
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ bugreportFinishedWithWarningTest(bugreportStateUnknown);
}
@Test
public void testBugreportFinished_withWarningShowAgain() throws Exception {
- bugreportFinishedWithWarningTest(STATE_SHOW);
+ int bugreportStateShow = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
+ bugreportFinishedWithWarningTest(bugreportStateShow);
}
private void bugreportFinishedWithWarningTest(Integer propertyState) throws Exception {
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
if (propertyState == null) {
// Clear properties
- mContext.getSharedPreferences(PREFS_BUGREPORT, Context.MODE_PRIVATE)
- .edit().clear().commit();
+ mContext.getSharedPreferences(
+ mContext.getResources().getString(com.android.internal.R.string.prefs_bugreport)
+ , Context.MODE_PRIVATE).edit().clear().commit();
// Confidence check...
- assertEquals("Did not reset properties", STATE_UNKNOWN,
- getWarningState(mContext, STATE_UNKNOWN));
+ assertEquals("Did not reset properties", bugreportStateUnknown,
+ getWarningState(mContext, bugreportStateUnknown));
} else {
setWarningState(mContext, propertyState);
}
@@ -501,7 +507,8 @@ public class BugreportReceiverTest {
// TODO: get ok and dontShowAgain from the dialog reference above
UiObject dontShowAgain =
mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm_dont_repeat));
- final boolean firstTime = propertyState == null || propertyState == STATE_UNKNOWN;
+ final boolean firstTime =
+ propertyState == null || propertyState == bugreportStateUnknown;
if (firstTime) {
if (Build.IS_USER) {
assertFalse("Checkbox should NOT be checked by default on user builds",
@@ -524,8 +531,8 @@ public class BugreportReceiverTest {
assertActionSendMultiple(extras);
// Make sure it's hidden now.
- int newState = getWarningState(mContext, STATE_UNKNOWN);
- assertEquals("Didn't change state", STATE_HIDE, newState);
+ int newState = getWarningState(mContext, bugreportStateUnknown);
+ assertEquals("Didn't change state", bugreportStateHide, newState);
}
@Test
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 2ca70558f18b..0b17a3f71bda 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
@@ -200,8 +200,9 @@ fun CommunalContainer(
scene(
CommunalScenes.Blank,
userActions =
- if (viewModel.v2FlagEnabled()) emptyMap()
- else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
+ if (viewModel.swipeToHubEnabled())
+ mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal)
+ else emptyMap(),
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
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 9c57efc24a22..418a7a52a97e 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
@@ -1705,15 +1705,38 @@ private fun Umo(
contentScope: ContentScope?,
modifier: Modifier = Modifier,
) {
- if (SceneContainerFlag.isEnabled && contentScope != null) {
- contentScope.MediaCarousel(
- modifier = modifier.fillMaxSize(),
- isVisible = true,
- mediaHost = viewModel.mediaHost,
- carouselController = viewModel.mediaCarouselController,
- )
- } else {
- UmoLegacy(viewModel, modifier)
+ val showNextActionLabel = stringResource(R.string.accessibility_action_label_umo_show_next)
+ val showPreviousActionLabel =
+ stringResource(R.string.accessibility_action_label_umo_show_previous)
+
+ Box(
+ modifier =
+ modifier.thenIf(!viewModel.isEditMode) {
+ Modifier.semantics {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(showNextActionLabel) {
+ viewModel.onShowNextMedia()
+ true
+ },
+ CustomAccessibilityAction(showPreviousActionLabel) {
+ viewModel.onShowPreviousMedia()
+ true
+ },
+ )
+ }
+ }
+ ) {
+ if (SceneContainerFlag.isEnabled && contentScope != null) {
+ contentScope.MediaCarousel(
+ modifier = modifier.fillMaxSize(),
+ isVisible = true,
+ mediaHost = viewModel.mediaHost,
+ carouselController = viewModel.mediaCarouselController,
+ )
+ } else {
+ UmoLegacy(viewModel, modifier)
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
index 62aa31b49870..73a24257580c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
@@ -50,7 +50,6 @@ import androidx.compose.ui.unit.times
import androidx.window.layout.WindowMetricsCalculator
import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_HEIGHT
import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_WIDTH
-import com.android.systemui.communal.util.WindowSizeUtils.MEDIUM_WIDTH
/**
* Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
@@ -267,9 +266,8 @@ fun calculateWindowSize(): DpSize {
}
private fun calculateNumCellsWidth(width: Dp) =
- // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
when {
- width >= MEDIUM_WIDTH -> 3
+ width >= 900.dp -> 3
width >= COMPACT_WIDTH -> 2
else -> 1
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 2e5b5b56c982..aad1276d76e5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -113,8 +113,8 @@ class DefaultClockProvider(
companion object {
// 750ms @ 120hz -> 90 frames of animation
- // In practice, 45 looks good enough
- const val NUM_CLOCK_FONT_ANIMATION_STEPS = 45
+ // In practice, 30 looks good enough and limits our memory usage
+ const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
val FLEX_TYPEFACE by lazy {
// TODO(b/364680873): Move constant to config_clockFontFamily when shipping
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index fe665e658feb..24b9e847d621 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -84,6 +84,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
import com.google.common.truth.Truth
import junit.framework.Assert
import kotlinx.coroutines.flow.MutableStateFlow
@@ -280,9 +281,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
kosmos.keyguardDismissTransitionInteractor,
{ primaryBouncerInteractor },
executor,
- ) {
- deviceEntryInteractor
- }
+ { deviceEntryInteractor },
+ { kosmos.windowRootViewBlurInteractor },
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 18cc8bf5f0d3..522650bcde3c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -23,24 +23,19 @@ import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
-import android.view.accessibility.AccessibilityManager
import android.view.accessibility.accessibilityManager
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
-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.fakeCommunalSmartspaceRepository
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.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -49,12 +44,15 @@ import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.mediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -62,73 +60,45 @@ import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalEditModeViewModelTest : SysuiTestCase() {
- @Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var packageManager: PackageManager
- @Mock private lateinit var metricsLogger: CommunalMetricsLogger
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private lateinit var tutorialRepository: FakeCommunalTutorialRepository
- private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
- private lateinit var mediaRepository: FakeCommunalMediaRepository
- private lateinit var communalSceneInteractor: CommunalSceneInteractor
- private lateinit var communalInteractor: CommunalInteractor
- private lateinit var accessibilityManager: AccessibilityManager
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testableResources = context.orCreateTestableResources
- private lateinit var underTest: CommunalEditModeViewModel
+ private val Kosmos.packageManager by Kosmos.Fixture { mock<PackageManager>() }
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- tutorialRepository = kosmos.fakeCommunalTutorialRepository
- widgetRepository = kosmos.fakeCommunalWidgetRepository
- smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
- mediaRepository = kosmos.fakeCommunalMediaRepository
- communalSceneInteractor = kosmos.communalSceneInteractor
- communalInteractor = spy(kosmos.communalInteractor)
- kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
- kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
- kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
- accessibilityManager = kosmos.accessibilityManager
+ private val Kosmos.metricsLogger by Kosmos.Fixture { mock<CommunalMetricsLogger>() }
- underTest =
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
CommunalEditModeViewModel(
communalSceneInteractor,
communalInteractor,
- kosmos.communalSettingsInteractor,
- kosmos.keyguardTransitionInteractor,
- mediaHost,
+ communalSettingsInteractor,
+ keyguardTransitionInteractor,
+ mock<MediaHost>(),
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
- kosmos.testDispatcher,
+ testDispatcher,
metricsLogger,
context,
accessibilityManager,
@@ -136,19 +106,28 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
WIDGET_PICKER_PACKAGE_NAME,
kosmos.mediaCarouselController,
)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
}
@Test
fun communalContent_onlyWidgetsAndCtaTileAreShownInEditMode() =
- testScope.runTest {
- tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ kosmos.runTest {
+ fakeCommunalTutorialRepository.setTutorialSettingState(
+ Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+ )
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, rank = 30)
- widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Smartspace available.
- smartspaceRepository.setTimers(
+ fakeCommunalSmartspaceRepository.setTimers(
listOf(
CommunalSmartspaceTimer(
smartspaceTargetId = "target",
@@ -159,7 +138,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
)
// Media playing.
- mediaRepository.mediaActive()
+ fakeCommunalMediaRepository.mediaActive()
val communalContent by collectLastValue(underTest.communalContent)
@@ -173,7 +152,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun selectedKey_onReorderWidgets_isSet() =
- testScope.runTest {
+ kosmos.runTest {
val selectedKey by collectLastValue(underTest.selectedKey)
underTest.setSelectedKey(null)
@@ -186,7 +165,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun isCommunalContentVisible_isTrue_whenEditModeShowing() =
- testScope.runTest {
+ kosmos.runTest {
val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
communalSceneInteractor.setEditModeState(EditModeState.SHOWING)
assertThat(isCommunalContentVisible).isEqualTo(true)
@@ -194,7 +173,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun isCommunalContentVisible_isFalse_whenEditModeNotShowing() =
- testScope.runTest {
+ kosmos.runTest {
val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
communalSceneInteractor.setEditModeState(null)
assertThat(isCommunalContentVisible).isEqualTo(false)
@@ -202,12 +181,14 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun deleteWidget() =
- testScope.runTest {
- tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ kosmos.runTest {
+ fakeCommunalTutorialRepository.setTutorialSettingState(
+ Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+ )
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, rank = 30)
- widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, rank = 20)
val communalContent by collectLastValue(underTest.communalContent)
@@ -233,26 +214,38 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
- fun reorderWidget_uiEventLogging_start() {
- underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
- verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
- }
+ fun reorderWidget_uiEventLogging_start() =
+ kosmos.runTest {
+ underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START.id)
+ }
@Test
- fun reorderWidget_uiEventLogging_end() {
- underTest.onReorderWidgetEnd()
- verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
- }
+ fun reorderWidget_uiEventLogging_end() =
+ kosmos.runTest {
+ underTest.onReorderWidgetEnd()
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH.id)
+ }
@Test
- fun reorderWidget_uiEventLogging_cancel() {
- underTest.onReorderWidgetCancel()
- verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
- }
+ fun reorderWidget_uiEventLogging_cancel() =
+ kosmos.runTest {
+ underTest.onReorderWidgetCancel()
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL.id)
+ }
@Test
fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
- testScope.runTest {
+ kosmos.runTest {
var activityStarted = false
val success =
underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
@@ -266,7 +259,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
- testScope.runTest {
+ kosmos.runTest {
val success =
underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
run { throw ActivityNotFoundException() }
@@ -278,7 +271,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun showDisclaimer_trueAfterEditModeShowing() =
- testScope.runTest {
+ kosmos.runTest {
val showDisclaimer by collectLastValue(underTest.showDisclaimer)
assertThat(showDisclaimer).isFalse()
@@ -288,9 +281,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun showDisclaimer_falseWhenDismissed() =
- testScope.runTest {
+ kosmos.runTest {
underTest.setEditModeState(EditModeState.SHOWING)
- kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
val showDisclaimer by collectLastValue(underTest.showDisclaimer)
@@ -301,63 +294,67 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun showDisclaimer_trueWhenTimeout() =
- testScope.runTest {
+ kosmos.runTest {
underTest.setEditModeState(EditModeState.SHOWING)
- kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
val showDisclaimer by collectLastValue(underTest.showDisclaimer)
assertThat(showDisclaimer).isTrue()
underTest.onDisclaimerDismissed()
assertThat(showDisclaimer).isFalse()
- advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+ testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS + 1.milliseconds)
assertThat(showDisclaimer).isTrue()
}
@Test
- fun scrollPosition_persistedOnEditCleanup() {
- val index = 2
- val offset = 30
- underTest.onScrollPositionUpdated(index, offset)
- underTest.cleanupEditModeState()
-
- verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
- }
+ fun scrollPosition_persistedOnEditCleanup() =
+ kosmos.runTest {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.cleanupEditModeState()
+
+ assertThat(communalInteractor.firstVisibleItemIndex).isEqualTo(index)
+ assertThat(communalInteractor.firstVisibleItemOffset).isEqualTo(offset)
+ }
@Test
- fun onNewWidgetAdded_accessibilityDisabled_doNothing() {
- whenever(accessibilityManager.isEnabled).thenReturn(false)
+ fun onNewWidgetAdded_accessibilityDisabled_doNothing() =
+ kosmos.runTest {
+ whenever(accessibilityManager.isEnabled).thenReturn(false)
- val provider =
- mock<AppWidgetProviderInfo> {
- on { loadLabel(packageManager) }.thenReturn("Test Clock")
- }
- underTest.onNewWidgetAdded(provider)
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
- verify(accessibilityManager, never()).sendAccessibilityEvent(any())
- }
+ verify(accessibilityManager, never()).sendAccessibilityEvent(any())
+ }
@Test
- fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() {
- whenever(accessibilityManager.isEnabled).thenReturn(true)
+ fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() =
+ kosmos.runTest {
+ whenever(accessibilityManager.isEnabled).thenReturn(true)
- val provider =
- mock<AppWidgetProviderInfo> {
- on { loadLabel(packageManager) }.thenReturn("Test Clock")
- }
- underTest.onNewWidgetAdded(provider)
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
- val captor = argumentCaptor<AccessibilityEvent>()
- verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
+ val captor = argumentCaptor<AccessibilityEvent>()
+ verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
- val event = captor.firstValue
- assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
- assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
- }
+ val event = captor.firstValue
+ assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+ assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
+ }
@Test
fun onResizeWidget_logsMetrics() =
- testScope.runTest {
+ kosmos.runTest {
val appWidgetId = 123
val spanY = 2
val widgetIdToRankMap = mapOf(appWidgetId to 1)
@@ -372,7 +369,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
rank = rank,
)
- verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
verify(metricsLogger)
.logResizeWidget(
componentName = componentName.flattenToString(),
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 85155157eda2..799054a92bee 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
@@ -78,6 +78,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -120,6 +121,7 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -161,6 +163,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
whenever(mediaHost.visible).thenReturn(true)
+ whenever(kosmos.mediaCarouselController.mediaCarouselScrollHandler)
+ .thenReturn(mediaCarouselScrollHandler)
kosmos.powerInteractor.setAwakeForTest()
@@ -187,6 +191,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
metricsLogger,
kosmos.mediaCarouselController,
kosmos.blurConfig,
+ false,
)
}
@@ -903,6 +908,20 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ fun onShowPreviousMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowPreviousMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(-1)
+ }
+
+ @Test
+ fun onShowNextMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowNextMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(1)
+ }
+
+ @Test
@EnableFlags(FLAG_BOUNCER_UI_REVAMP)
fun uiIsBlurred_whenPrimaryBouncerIsShowing() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index e13f3f12c55a..7e93f5a8c9a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -20,19 +20,26 @@ import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
@@ -56,11 +63,14 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -93,7 +103,10 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_SCENE_KTF_REFACTOR,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
}
}
@@ -107,6 +120,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
// Transition to DOZING and set the power interactor asleep.
kosmos.powerInteractor.setAsleepForTest()
+ kosmos.setCommunalV2ConfigEnabled(true)
runBlocking {
kosmos.transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
@@ -160,7 +174,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_GLANCEABLE_HUB_V2)
fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
kosmos.runTest {
whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
@@ -179,7 +193,17 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
kosmos.runTest {
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
clearInvocations(fakeCommunalSceneRepository)
powerInteractor.setAwakeForTest()
@@ -240,7 +264,17 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
kosmos.runTest {
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
// Device turns on.
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 8e1068226431..5882cff74eb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -19,14 +19,20 @@ package com.android.systemui.keyguard.domain.interactor
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -46,6 +52,8 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
@@ -66,7 +74,10 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_SCENE_KTF_REFACTOR,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
.andSceneContainer()
}
}
@@ -101,6 +112,7 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
)
reset(kosmos.transitionRepository)
kosmos.setCommunalAvailable(true)
+ kosmos.setCommunalV2ConfigEnabled(true)
}
kosmos.underTest.start()
}
@@ -202,7 +214,17 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
reset(transitionRepository)
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
// Device wakes up.
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index 47ca4b14a26f..f357d0c80822 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -69,7 +69,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
@Parameters(
name =
"canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
- " isSingleShade={3}, isShadeTouchable={4}, isOccluded={6}"
+ " isSingleShade={3}, isShadeTouchable={4}, isOccluded={5}"
)
@JvmStatic
fun combinations() = buildList {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index d073cf1ac9db..c2f0ab92b32b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.media.controls.ui.view
+import android.content.res.Resources
import android.testing.TestableLooper
import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,16 +28,21 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -42,7 +50,9 @@ import org.mockito.MockitoAnnotations
class MediaCarouselScrollHandlerTest : SysuiTestCase() {
private val carouselWidth = 1038
+ private val settingsButtonWidth = 200
private val motionEventUp = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ private lateinit var testableLooper: TestableLooper
@Mock lateinit var mediaCarousel: MediaScrollView
@Mock lateinit var pageIndicator: PageIndicator
@@ -53,6 +63,9 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var logSmartspaceImpression: (Boolean) -> Unit
@Mock lateinit var logger: MediaUiEventLogger
+ @Mock lateinit var contentContainer: ViewGroup
+ @Mock lateinit var settingsButton: View
+ @Mock lateinit var resources: Resources
lateinit var executor: FakeExecutor
private val clock = FakeSystemClock()
@@ -63,6 +76,11 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(clock)
+ testableLooper = TestableLooper.get(this)
+ PhysicsAnimatorTestUtils.prepareForTest()
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+ whenever(mediaCarousel.contentContainer).thenReturn(contentContainer)
mediaCarouselScrollHandler =
MediaCarouselScrollHandler(
mediaCarousel,
@@ -74,13 +92,17 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
closeGuts,
falsingManager,
logSmartspaceImpression,
- logger
+ logger,
)
mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth
-
whenever(mediaCarousel.touchListener).thenReturn(mediaCarouselScrollHandler.touchListener)
}
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
@Test
fun testCarouselScroll_shortScroll() {
whenever(mediaCarousel.isLayoutRtl).thenReturn(false)
@@ -128,4 +150,109 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
}
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(carouselWidth), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart_isRTL() {
+ setupMediaContainer(visibleIndex = 0)
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd_isRTL() {
+ setupMediaContainer(visibleIndex = 1)
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testScrollByStep_noScroll_notDismissible() {
+ setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel, never()).animationTargetX = anyFloat()
+ }
+
+ private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
+ whenever(contentContainer.childCount).thenReturn(2)
+ val child1: View = mock()
+ val child2: View = mock()
+ whenever(child1.left).thenReturn(0)
+ whenever(child2.left).thenReturn(carouselWidth)
+ whenever(contentContainer.getChildAt(0)).thenReturn(child1)
+ whenever(contentContainer.getChildAt(1)).thenReturn(child2)
+
+ whenever(settingsButton.width).thenReturn(settingsButtonWidth)
+ whenever(settingsButton.context).thenReturn(context)
+ whenever(settingsButton.resources).thenReturn(resources)
+ whenever(settingsButton.resources.getDimensionPixelSize(anyInt())).thenReturn(20)
+ mediaCarouselScrollHandler.onSettingsButtonUpdated(settingsButton)
+
+ mediaCarouselScrollHandler.visibleMediaIndex = visibleIndex
+ mediaCarouselScrollHandler.showsSettingsButton = showsSettingsButton
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index 264eda5a07eb..668c606677ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.settings.userFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -76,11 +77,11 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
@Test
fun setLargeTilesSpecs_inSharedPreferences() {
val setA = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ underTest.writeLargeTileSpecs(setA.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
val setB = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ underTest.writeLargeTileSpecs(setB.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
}
@@ -92,12 +93,12 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
val setA = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ underTest.writeLargeTileSpecs(setA.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
val setB = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ underTest.writeLargeTileSpecs(setB.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -106,7 +107,7 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
}
@Test
- fun setInitialTilesFromSettings_noLargeTiles_tilesSet() =
+ fun setUpgradePathFromSettings_noLargeTiles_tilesSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -117,14 +118,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
- underTest.setInitialLargeTilesSpecs(tiles, PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(tiles)
}
}
@Test
- fun setInitialTilesFromSettings_alreadyLargeTiles_tilesNotSet() =
+ fun setUpgradePathFromSettings_alreadyLargeTiles_tilesNotSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -133,14 +137,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
setLargeTilesSpecsInSharedPreferences(setOf("tileC"))
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(setOf("tileC").toTileSpecs())
}
}
@Test
- fun setInitialTilesFromSettings_emptyLargeTiles_tilesNotSet() =
+ fun setUpgradePathFromSettings_emptyLargeTiles_tilesNotSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -149,14 +156,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
setLargeTilesSpecsInSharedPreferences(emptySet())
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEmpty()
}
}
@Test
- fun setInitialTilesFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
+ fun setUpgradePathFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -164,7 +174,10 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setUserInfos(USERS)
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
@@ -174,7 +187,7 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
}
@Test
- fun setInitialTiles_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
+ fun setUpgradePath_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -186,14 +199,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
assertThat(currentLargeTiles).isNotEmpty()
val tiles = setOf("tileA", "tileB")
- underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(tiles.toTileSpecs())
}
}
@Test
- fun setInitialTiles_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
+ fun setUpgradePath_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -204,15 +220,80 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
assertThat(currentLargeTiles).isNotEmpty()
- underTest.setLargeTilesSpecs(setOf(TileSpec.create("tileC")))
+ underTest.writeLargeTileSpecs(setOf(TileSpec.create("tileC")))
val tiles = setOf("tileA", "tileB")
- underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(setOf(TileSpec.create("tileC")))
}
}
+ @Test
+ fun setTilesRestored_noLargeTiles_tilesSet() =
+ with(kosmos) {
+ testScope.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+ assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+ }
+
+ @Test
+ fun setDefaultTilesInitial_defaultSetLarge() =
+ with(kosmos) {
+ testScope.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.DefaultSet,
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+ }
+
+ @Test
+ fun setTilesRestored_afterDefaultSet_tilesSet() =
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.DefaultSet,
+ PRIMARY_USER_ID,
+ )
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+ }
+
private fun getSharedPreferences(): SharedPreferences =
with(kosmos) {
return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
new file mode 100644
index 000000000000..f3c1f0c9dba8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2025 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.qs.panels.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.res.mainResources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.shared.model.PackageChangeModel.Empty.packageName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
+import com.android.systemui.qs.pipeline.data.repository.defaultTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
+import com.android.systemui.settings.userFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LargeTilesUpgradePathsTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply { defaultTilesRepository = DefaultTilesQSHostRepository(mainResources) }
+
+ private val defaultTiles = kosmos.defaultTilesRepository.defaultTiles.toSet()
+
+ private val underTest = kosmos.qsPreferencesInteractor
+
+ private val Kosmos.userId
+ get() = userRepository.getSelectedUserInfo().id
+
+ private val Kosmos.intent
+ get() =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, kosmos.userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
+
+ /**
+ * This test corresponds to the case of a fresh start.
+ *
+ * The resulting large tiles are the default set of large tiles.
+ */
+ @Test
+ fun defaultTiles_noDataInSharedPreferences_defaultLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded in place from a build that didn't support large
+ * tiles to one that does. The current tiles of the user are read from settings.
+ *
+ * The resulting large tiles are those that were read from Settings.
+ */
+ @Test
+ fun upgradeInPlace_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a fresh start, and then the user restarts the device, without ever
+ * having modified the set of large tiles.
+ *
+ * The resulting large tiles are the default large tiles that were set on the fresh start
+ */
+ @Test
+ fun defaultSet_restartDevice_largeTilesDontChange() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ // User restarts the device, this will send a read from settings with the default
+ // set of tiles
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(defaultTiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+
+ /**
+ * This test corresponds to a fresh start, following the user changing the sizes of some tiles.
+ * After that, the user restarts the device.
+ *
+ * The resulting set of large tiles are those that the user determined before restarting the
+ * device.
+ */
+ @Test
+ fun defaultSet_someSizeChanges_restart_correctSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ underTest.setLargeTilesSpecs(largeTiles!! + setOf("a", "b").toTileSpecs())
+ val largeTilesBeforeRestart = largeTiles!!
+
+ // Restart
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(defaultTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEqualTo(largeTilesBeforeRestart)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded, and after that performed some size changes.
+ * After that, the user restarts the device.
+ *
+ * The resulting set of large tiles are those that the user determined before restarting the
+ * device.
+ */
+ @Test
+ fun readFromSettings_changeSizes_restart_newLargeSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ underTest.setLargeTilesSpecs(emptySet())
+
+ assertThat(largeTiles).isEmpty()
+
+ // Restart
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEmpty()
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. Note that there's no file in SharedPreferences to
+ * restore.
+ *
+ * The resulting set of large tiles are those that were restored from the backup.
+ */
+ @Test
+ fun restoreFromBackup_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. However, the restore happens after SystemUI's
+ * initialization has set the tiles to default. Note that there's no file in SharedPreferences
+ * to restore.
+ *
+ * The resulting set of large tiles are those that were restored from the backup.
+ */
+ @Test
+ fun restoreFromBackup_afterDefault_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a user that restored from a build that supported different sizes
+ * tiles. First the list of tiles is restored in Settings and then a file containing some large
+ * tiles overrides the current shared preferences file
+ *
+ * The resulting set of large tiles are those that were restored from the shared preferences
+ * backup (and not the full list).
+ */
+ @Test
+ fun restoreFromBackup_thenRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ val tilesFromBackupOfSharedPrefs = setOf("a")
+ setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+ }
+
+ /**
+ * This test corresponds to a user that restored from a build that supported different sizes
+ * tiles. However, this restore of settings happened after SystemUI's restore of the SharedPrefs
+ * containing the user's previous selections to large/small tiles.
+ *
+ * The resulting set of large tiles are those that were restored from the shared preferences
+ * backup (and not the full list).
+ */
+ @Test
+ fun restoreFromBackup_afterRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+ val tilesFromBackupOfSharedPrefs = setOf("a")
+
+ setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. After that, the user modifies the size of some tiles
+ * and then restarts the device.
+ *
+ * The resulting set of large tiles are those after the user modifications.
+ */
+ @Test
+ fun restoreFromBackup_changeSizes_restart_newLargeSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(readTiles),
+ userId,
+ )
+ underTest.setLargeTilesSpecs(emptySet())
+
+ assertThat(largeTiles).isEmpty()
+
+ // Restart
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEmpty()
+ }
+
+ private companion object {
+ private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+
+ private fun Kosmos.getSharedPreferences(): SharedPreferences =
+ userFileManager.getSharedPreferences(
+ QSPreferencesRepository.FILE_NAME,
+ Context.MODE_PRIVATE,
+ userRepository.getSelectedUserInfo().id,
+ )
+
+ private fun Kosmos.setLargeTilesSpecsInSharedPreferences(specs: Set<String>) {
+ getSharedPreferences().edit().putStringSet(LARGE_TILES_SPECS_KEY, specs).apply()
+ }
+
+ private fun Kosmos.getLargeTilesSpecsFromSharedPreferences(): Set<String> {
+ return getSharedPreferences().getStringSet(LARGE_TILES_SPECS_KEY, emptySet())!!
+ }
+
+ private fun Set<String>.toTileSpecs(): Set<TileSpec> {
+ return map { TileSpec.create(it) }.toSet()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index 79acfdaa415b..9838bcb86684 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -66,7 +66,7 @@ class IconTilesInteractorTest : SysuiTestCase() {
runCurrent()
// Resize it to large
- qsPreferencesRepository.setLargeTilesSpecs(setOf(spec))
+ qsPreferencesRepository.writeLargeTileSpecs(setOf(spec))
runCurrent()
// Assert that the new tile was added to the large tiles set
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 4b8cd3742bff..d9b3926fa215 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.FakeRetailModeRepository
@@ -242,9 +243,12 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
storeTilesForUser(startingTiles, userId)
val tiles by collectLastValue(underTest.tilesSpecs(userId))
- val tilesRead by collectLastValue(underTest.tilesReadFromSetting.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
- assertThat(tilesRead).isEqualTo(startingTiles.toTileSpecs().toSet() to userId)
+ assertThat(tilesRead)
+ .isEqualTo(
+ TilesUpgradePath.ReadFromSettings(startingTiles.toTileSpecs().toSet()) to userId
+ )
}
@Test
@@ -258,13 +262,13 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
val tiles10 by collectLastValue(underTest.tilesSpecs(10))
val tiles11 by collectLastValue(underTest.tilesSpecs(11))
- val tilesRead by collectValues(underTest.tilesReadFromSetting.consumeAsFlow())
+ val tilesRead by collectValues(underTest.tilesUpgradePath.consumeAsFlow())
assertThat(tilesRead).hasSize(2)
assertThat(tilesRead)
.containsExactly(
- startingTiles10.toTileSpecs().toSet() to 10,
- startingTiles11.toTileSpecs().toSet() to 11,
+ TilesUpgradePath.ReadFromSettings(startingTiles10.toTileSpecs().toSet()) to 10,
+ TilesUpgradePath.ReadFromSettings(startingTiles11.toTileSpecs().toSet()) to 11,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index 1945f750efaf..29bd18d3f3a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -7,8 +7,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.data.model.RestoreData
-import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepositoryTest.Companion.toTilesSet
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -352,11 +352,11 @@ class UserTileSpecRepositoryTest : SysuiTestCase() {
@Test
fun noSettingsStored_noTilesReadFromSettings() =
testScope.runTest {
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
val tiles by collectLastValue(underTest.tiles())
assertThat(tiles).isEqualTo(getDefaultTileSpecs())
- assertThat(tilesRead).isEqualTo(null)
+ assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
}
@Test
@@ -365,19 +365,20 @@ class UserTileSpecRepositoryTest : SysuiTestCase() {
val storedTiles = "a,b"
storeTiles(storedTiles)
val tiles by collectLastValue(underTest.tiles())
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
- assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
}
@Test
fun noSettingsStored_tilesChanged_tilesReadFromSettingsNotChanged() =
testScope.runTest {
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
val tiles by collectLastValue(underTest.tiles())
underTest.addTile(TileSpec.create("a"))
- assertThat(tilesRead).isEqualTo(null)
+ assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
}
@Test
@@ -386,10 +387,34 @@ class UserTileSpecRepositoryTest : SysuiTestCase() {
val storedTiles = "a,b"
storeTiles(storedTiles)
val tiles by collectLastValue(underTest.tiles())
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
underTest.addTile(TileSpec.create("c"))
- assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
+ }
+
+ @Test
+ fun tilesRestoredFromBackup() =
+ testScope.runTest {
+ val specsBeforeRestore = "a,b,c,d,e"
+ val restoredSpecs = "a,c,d,f"
+ val autoAddedBeforeRestore = "b,d"
+ val restoredAutoAdded = "d,e"
+
+ storeTiles(specsBeforeRestore)
+ val tiles by collectLastValue(underTest.tiles())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
+ runCurrent()
+
+ val restoreData =
+ RestoreData(restoredSpecs.toTileSpecs(), restoredAutoAdded.toTilesSet(), USER)
+ underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet())
+ runCurrent()
+
+ val expected = "a,b,c,d,f"
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.RestoreFromBackup(expected.toTilesSet()))
}
private fun getDefaultTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index c1477fe52f0e..a9f3a655ada9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -80,6 +80,8 @@ class WorkProfilePolicyTest {
// Set desktop mode supported
whenever(mContext.resources).thenReturn(mResources)
whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+ whenever(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops))
+ .thenReturn(true)
policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index dbe8f8226d43..c7b3175a636f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -31,7 +31,6 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -265,91 +264,25 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- fun chip_positiveStartTime_notPromoted_colorsAreThemed() =
+ fun chip_positiveStartTime_colorsAreAccentThemed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null))
assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
+ .isEqualTo(ColorsModel.AccentThemed)
}
@Test
- fun chip_zeroStartTime_notPromoted_colorsAreThemed() =
+ fun chip_zeroStartTime_colorsAreAccentThemed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null))
assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
- }
-
- @Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_positiveStartTime_promoted_notifChipsFlagOff_colorsAreThemed() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
- }
-
- @Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreThemed() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_positiveStartTime_promoted_notifChipsFlagOn_colorsAreCustom() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(
- ColorsModel.Custom(
- backgroundColorInt = PROMOTED_BACKGROUND_COLOR,
- primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR,
- )
- )
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreCustom() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(
- ColorsModel.Custom(
- backgroundColorInt = PROMOTED_BACKGROUND_COLOR,
- primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR,
- )
- )
+ .isEqualTo(ColorsModel.AccentThemed)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 192ad879891f..aaa9b58a45df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -186,7 +186,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_onePromotedNotif_colorMatches() =
+ fun chips_onePromotedNotif_colorIsSystemThemed() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -209,10 +209,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
)
assertThat(latest).hasSize(1)
- val colors = latest!![0].colors
- assertThat(colors).isInstanceOf(ColorsModel.Custom::class.java)
- assertThat((colors as ColorsModel.Custom).backgroundColorInt).isEqualTo(56)
- assertThat((colors as ColorsModel.Custom).primaryTextColorInt).isEqualTo(89)
+ assertThat(latest!![0].colors).isEqualTo(ColorsModel.SystemThemed)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
index d727089094f0..9ec5a42714bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
@@ -52,24 +52,13 @@ class ChipTextTruncationHelperTest : SysuiTestCase() {
}
@Test
- fun shouldShowText_desiredSlightlyLargerThanMax_true() {
+ fun shouldShowText_desiredMoreThanMax_false() {
val result =
underTest.shouldShowText(
desiredTextWidthPx = (MAX_WIDTH * 1.1).toInt(),
widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
)
- assertThat(result).isTrue()
- }
-
- @Test
- fun shouldShowText_desiredMoreThanTwiceMax_false() {
- val result =
- underTest.shouldShowText(
- desiredTextWidthPx = (MAX_WIDTH * 2.2).toInt(),
- widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
- )
-
assertThat(result).isFalse()
}
@@ -80,8 +69,8 @@ class ChipTextTruncationHelperTest : SysuiTestCase() {
View.MeasureSpec.makeMeasureSpec(MAX_WIDTH / 2, View.MeasureSpec.AT_MOST)
)
- // WHEN desired is more than twice the smallerWidthSpec
- val desiredWidth = (MAX_WIDTH * 1.1).toInt()
+ // WHEN desired is more than the smallerWidthSpec
+ val desiredWidth = ((MAX_WIDTH / 2) * 1.1).toInt()
val result =
underTest.shouldShowText(
@@ -100,8 +89,8 @@ class ChipTextTruncationHelperTest : SysuiTestCase() {
View.MeasureSpec.makeMeasureSpec(MAX_WIDTH * 3, View.MeasureSpec.AT_MOST)
)
- // WHEN desired is more than twice the max
- val desiredWidth = (MAX_WIDTH * 2.2).toInt()
+ // WHEN desired is more than the max
+ val desiredWidth = (MAX_WIDTH * 1.1).toInt()
val result =
underTest.shouldShowText(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index 60030ad4e428..e3a84fd2c2eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -54,7 +54,7 @@ class ChipTransitionHelperTest : SysuiTestCase() {
OngoingActivityChipModel.Active.Timer(
key = KEY,
icon = createIcon(R.drawable.ic_cake),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
startTimeMs = 100L,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
@@ -68,7 +68,7 @@ class ChipTransitionHelperTest : SysuiTestCase() {
OngoingActivityChipModel.Active.IconOnly(
key = KEY,
icon = createIcon(R.drawable.ic_hotspot),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
)
@@ -90,7 +90,7 @@ class ChipTransitionHelperTest : SysuiTestCase() {
OngoingActivityChipModel.Active.Timer(
key = KEY,
icon = createIcon(R.drawable.ic_cake),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
startTimeMs = 100L,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
@@ -132,7 +132,7 @@ class ChipTransitionHelperTest : SysuiTestCase() {
OngoingActivityChipModel.Active.Timer(
key = KEY,
icon = createIcon(R.drawable.ic_cake),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
startTimeMs = 100L,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 403ac3288128..20637cd4af33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,7 +293,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
- fun chipsLegacy_twoTimerChips_isSmallPortrait_andChipsModernizationDisabled_bothSquished() =
+ fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
addOngoingCallState(key = "call")
@@ -307,6 +307,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_twoTimerChips_isSmallPortrait_bothSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ val latest by collectLastValue(underTest.chips)
+
+ // Squished chips are icon only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
@@ -324,6 +340,23 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000)
+ addOngoingCallState(key = "call")
+
+ val latest by collectLastValue(underTest.chips)
+
+ // The screen record countdown isn't squished to icon-only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Countdown::class.java)
+ // But the call chip *is* squished
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
@@ -360,6 +393,38 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ // WHEN there's only one chip
+ screenRecordState.value = ScreenRecordModel.Recording
+ removeOngoingCallState(key = "call")
+
+ // The screen record isn't squished because it's the only one
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+
+ // WHEN there's 2 chips
+ addOngoingCallState(key = "call")
+
+ // THEN they both become squished
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ // But the call chip *is* squished
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+
+ // WHEN we go back down to 1 chip
+ screenRecordState.value = ScreenRecordModel.DoingNothing
+
+ // THEN the remaining chip unsquishes
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_twoChips_isLandscape_notSquished() =
@@ -383,6 +448,29 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_twoChips_isLandscape_notSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ // WHEN we're in landscape
+ val config =
+ Configuration(kosmos.mainResources.configuration).apply {
+ orientation = Configuration.ORIENTATION_LANDSCAPE
+ }
+ kosmos.fakeConfigurationRepository.onConfigurationChange(config)
+
+ val latest by collectLastValue(underTest.chips)
+
+ // THEN the chips aren't squished (squished chips would be icon only)
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_twoChips_isLargeScreen_notSquished() =
@@ -402,16 +490,19 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
- @Test
@EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun chips_twoChips_chipsModernizationEnabled_notSquished() =
+ @Test
+ fun chips_twoChips_isLargeScreen_notSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
addOngoingCallState(key = "call")
+ // WHEN we're on a large screen
+ kosmos.displayStateRepository.setIsLargeScreen(true)
+
val latest by collectLastValue(underTest.chips)
- // Squished chips would be icon only
+ // THEN the chips aren't squished (squished chips would be icon only)
assertThat(latest!!.active[0])
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat(latest!!.active[1])
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 57b7df7a8d31..31f8590c0378 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -51,11 +51,13 @@ import com.android.systemui.res.R
import com.android.systemui.shade.ShadeViewStateProvider
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
import com.android.systemui.statusbar.layout.mockStatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -85,6 +87,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
+@DisableFlags(NewStatusBarIcons.FLAG_NAME)
class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
private lateinit var kosmos: Kosmos
private lateinit var testScope: TestScope
@@ -190,6 +193,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
statusBarIconController,
iconManagerFactory,
batteryMeterViewController,
+ kosmos.batteryViewModelFactory,
shadeViewStateProvider,
keyguardStateController,
keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 4759c081de5f..183cd8f1ae8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.graphics.Color
import android.graphics.Rect
import android.view.View
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -26,15 +29,20 @@ import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChip
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import org.mockito.Mockito.mock
class FakeHomeStatusBarViewModel(
override val operatorNameViewModel: StatusBarOperatorNameViewModel
-) : HomeStatusBarViewModel {
+) : HomeStatusBarViewModel, ExclusiveActivatable() {
+ private val hydrator = Hydrator("FakeHomeStatusBarViewModel.hydrator")
+
override val areNotificationsLightsOut = MutableStateFlow(false)
override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
@@ -56,6 +64,11 @@ class FakeHomeStatusBarViewModel(
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
+ override val batteryViewModelFactory: BatteryViewModel.Factory =
+ object : BatteryViewModel.Factory {
+ override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
+ }
+
override val shouldShowOperatorNameView = MutableStateFlow(false)
override val isClockVisible =
@@ -80,6 +93,7 @@ class FakeHomeStatusBarViewModel(
var darkIconTint = Color.BLACK
var lightIconTint = Color.WHITE
+ var darkIntensity = 0f
override val areaTint: Flow<StatusBarTintColor> =
MutableStateFlow(
@@ -91,4 +105,22 @@ class FakeHomeStatusBarViewModel(
}
}
)
+
+ val isAreaDarkSource =
+ MutableStateFlow(
+ IsAreaDark { viewBounds ->
+ if (DarkIconDispatcher.isInAreas(darkRegions, viewBounds)) {
+ darkIntensity < 0.5f
+ } else {
+ false
+ }
+ }
+ )
+
+ override val areaDark: IsAreaDark by
+ hydrator.hydratedStateOf(traceName = "areaDark", source = isAreaDarkSource)
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 354edac75452..36e18e653f20 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -56,7 +56,6 @@ import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImp
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.unfoldedDeviceState
import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -79,8 +78,10 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
+import org.mockito.kotlin.verifyNoMoreInteractions
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -603,12 +604,39 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
}
}
+ @Test
+ fun foldingStarted_screenStillOn_eventSentOnlyAfterScreenSwitches() {
+ // can happen for both folding and unfolding (with animations off) but it's more likely to
+ // happen when folding as waiting for screen on is the default case then
+ testScope.runTest {
+ startInUnfoldedState(displaySwitchLatencyTracker)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verifyNoMoreInteractions(displaySwitchLatencyLogger)
+
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
+ runCurrent()
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verify(displaySwitchLatencyLogger).log(any())
+ }
+ }
+
private suspend fun TestScope.startInFoldedState(tracker: DisplaySwitchLatencyTracker) {
setDeviceState(FOLDED)
tracker.start()
runCurrent()
}
+ private suspend fun TestScope.startInUnfoldedState(tracker: DisplaySwitchLatencyTracker) {
+ setDeviceState(UNFOLDED)
+ tracker.start()
+ runCurrent()
+ }
+
private suspend fun TestScope.startUnfolding() {
setDeviceState(HALF_FOLDED)
powerInteractor.setScreenPowerState(SCREEN_OFF)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 75f3386ed695..b8e19248b2de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -47,6 +47,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.TestScopeProvider;
+import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -268,13 +269,15 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
@Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
- mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
+ var sessionId = MediaSessions.SessionId.Companion.from(token);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(sessionId, 0);
}
@Test
public void testOnRemoteRemove_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
- mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
+ var sessionId = MediaSessions.SessionId.Companion.from(token);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(sessionId);
}
@Test
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
new file mode 100644
index 000000000000..1ba637f379c1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 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.
+ -->
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/clipboard_minimized_background"
+ android:inset="4dp"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 448b3e7d5ea0..915563b1ae20 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -171,12 +171,12 @@
android:layout_height="wrap_content"
android:visibility="gone"
android:elevation="7dp"
- android:padding="8dp"
+ android:padding="12dp"
app:layout_constraintBottom_toTopOf="@id/indication_container"
app:layout_constraintStart_toStartOf="parent"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:background="@drawable/clipboard_minimized_background">
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="2dp"
+ android:background="@drawable/clipboard_minimized_background_inset">
<ImageView
android:src="@drawable/ic_content_paste"
android:tint="?attr/overlayButtonTextColor"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index b9ef88eea6b9..32407c6ae1bf 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -147,6 +147,7 @@ frame when animating QS <-> QQS transition
android:id="@+id/batteryRemainingIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:visibility="gone"
app:textAppearance="@style/TextAppearance.QS.Status" />
</LinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml b/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
index 6f42286d9fac..b66a88a3e523 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
@@ -43,9 +43,6 @@
ongoing_activity_chip_short_time_delta] will ever be shown at one time. -->
<!-- Shows a timer, like 00:01. -->
- <!-- Don't use the LimitedWidth style for the timer because the end of the timer is often
- the most important value. ChipChronometer has the correct logic for when the timer is
- too large for the space allowed. -->
<com.android.systemui.statusbar.chips.ui.view.ChipChronometer
android:id="@+id/ongoing_activity_chip_time"
style="@style/StatusBar.Chip.Text"
@@ -54,14 +51,14 @@
<!-- Shows generic text. -->
<com.android.systemui.statusbar.chips.ui.view.ChipTextView
android:id="@+id/ongoing_activity_chip_text"
- style="@style/StatusBar.Chip.Text.LimitedWidth"
+ style="@style/StatusBar.Chip.Text"
android:visibility="gone"
/>
<!-- Shows a time delta in short form, like "15min" or "1hr". -->
<com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
android:id="@+id/ongoing_activity_chip_short_time_delta"
- style="@style/StatusBar.Chip.Text.LimitedWidth"
+ style="@style/StatusBar.Chip.Text"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index c28dc50cc1dc..bb99d581c0b0 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -34,12 +34,17 @@
android:orientation="horizontal"/>
<!-- PaddingEnd is added to balance hover padding, compensating for paddingStart in statusIcons.
- See b/339589733 -->
+ See b/339589733.
+
+ Default visibility is now "gone" to make space for the new battery icon
+ -->
<com.android.systemui.battery.BatteryMeterView android:id="@+id/battery"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:clipToPadding="false"
android:clipChildren="false"
android:paddingEnd="@dimen/status_bar_battery_end_padding"
+ android:visibility="gone"
systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
+
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 67f620f6fc54..8ad99abccdfe 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,7 +16,7 @@
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/volume_dialog_root"
+ android:id="@+id/volume_dialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
index 6748cfa05c35..4e3c8cc4413b 100644
--- a/packages/SystemUI/res/layout/volume_ringer_button.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -13,20 +13,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" >
-
- <ImageButton
- android:id="@+id/volume_drawer_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
- android:contentDescription="@string/volume_ringer_mode"
- android:gravity="center"
- android:tint="@androidprv:color/materialColorOnSurface"
- android:src="@drawable/volume_ringer_item_bg"
- android:background="@drawable/volume_ringer_item_bg"/>
-
-</FrameLayout>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/volume_ringer_item_bg"
+ android:contentDescription="@string/volume_ringer_mode"
+ android:gravity="center"
+ android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+ android:src="@drawable/volume_ringer_item_bg"
+ android:tint="@androidprv:color/materialColorOnSurface" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9b8926e921c9..09aa2241e42b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1110,4 +1110,7 @@
<!-- Configuration for wallpaper focal area -->
<bool name="center_align_focal_area_shape">false</bool>
<string name="focal_area_target" translatable="false" />
+
+ <!-- Configuration to swipe to open glanceable hub -->
+ <bool name="config_swipeToOpenGlanceableHub">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2d3c07b93cb1..648e4c2e3ac7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1811,6 +1811,7 @@
<dimen name="ongoing_activity_chip_text_end_padding_for_embedded_padding_icon">6dp</dimen>
<dimen name="ongoing_activity_chip_text_fading_edge_length">12dp</dimen>
<dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
+ <dimen name="ongoing_activity_chip_outline_width">2px</dimen>
<!-- Status bar user chip -->
<dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d18a90a17abe..86292039d93d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1351,6 +1351,10 @@
<string name="accessibility_action_label_shrink_widget">Decrease height</string>
<!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_expand_widget">Increase height</string>
+ <!-- Label for accessibility action to show the next media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_next">Show next</string>
+ <!-- Label for accessibility action to show the previous media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_previous">Show previous</string>
<!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
<!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7f2c89346423..4961a7ece69a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -93,15 +93,6 @@
<item name="android:textColor">?android:attr/colorPrimary</item>
</style>
- <!-- Style for a status bar chip text that has a maximum width. Since there's so little room in
- the status bar chip area, don't ellipsize the text and instead just fade it out a bit at
- the end. -->
- <style name="StatusBar.Chip.Text.LimitedWidth">
- <item name="android:ellipsize">none</item>
- <item name="android:requiresFadingEdge">horizontal</item>
- <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item>
- </style>
-
<style name="Chipbar" />
<style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 335a910eb106..73dc28230e65 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -98,7 +98,6 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Flags;
import com.android.systemui.FontStyles;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -121,6 +120,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
static final int USER_TYPE_PRIMARY = 1;
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
+ private boolean mTransparentModeEnabled = false;
@IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
@@ -814,15 +814,30 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
mDisappearAnimRunning = false;
}
+ /**
+ * Make the bouncer background transparent
+ */
+ public void enableTransparentMode() {
+ mTransparentModeEnabled = true;
+ reloadBackgroundColor();
+ }
+
+ /**
+ * Make the bouncer background opaque
+ */
+ public void disableTransparentMode() {
+ mTransparentModeEnabled = false;
+ reloadBackgroundColor();
+ }
+
private void reloadBackgroundColor() {
- if (Flags.bouncerUiRevamp()) {
- // Keep the background transparent, otherwise the background color looks like a box
- // while scaling the bouncer for back animation or while transitioning to the bouncer.
+ if (mTransparentModeEnabled) {
setBackgroundColor(Color.TRANSPARENT);
} else {
setBackgroundColor(
getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim));
}
+ invalidate();
}
void reloadColors() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ff7b2b025539..d10fce416150 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -70,6 +70,7 @@ import com.android.keyguard.KeyguardSecurityContainer.SwipeListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -96,6 +97,8 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository;
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
import dagger.Lazy;
@@ -134,6 +137,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final FalsingA11yDelegate mFalsingA11yDelegate;
private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
+ private final Lazy<WindowRootViewBlurInteractor> mRootViewBlurInteractor;
private int mTranslationY;
private final KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
private final DevicePolicyManager mDevicePolicyManager;
@@ -431,6 +435,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final Executor mBgExecutor;
@Nullable
private Job mSceneTransitionCollectionJob;
+ private Job mBlurEnabledCollectionJob;
@Inject
public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -463,9 +468,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
@Background Executor bgExecutor,
- Provider<DeviceEntryInteractor> deviceEntryInteractor
+ Provider<DeviceEntryInteractor> deviceEntryInteractor,
+ Lazy<WindowRootViewBlurInteractor> rootViewBlurInteractorProvider
) {
super(view);
+ mRootViewBlurInteractor = rootViewBlurInteractorProvider;
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -539,6 +546,32 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
);
}
+
+ if (Flags.bouncerUiRevamp()) {
+ mBlurEnabledCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+ mRootViewBlurInteractor.get().isBlurCurrentlySupported(),
+ this::handleBlurSupportedChanged);
+ }
+ }
+
+ private void handleBlurSupportedChanged(boolean isWindowBlurSupported) {
+ if (isWindowBlurSupported) {
+ mView.enableTransparentMode();
+ } else {
+ mView.disableTransparentMode();
+ }
+ }
+
+ private void refreshBouncerBackground() {
+ // This is present solely for screenshot tests that disable blur by invoking setprop to
+ // disable blurs, however the mRootViewBlurInteractor#isBlurCurrentlySupported doesn't emit
+ // an updated value because sysui doesn't have a way to register for changes to setprop.
+ // KeyguardSecurityContainer view is inflated only once and doesn't re-inflate so it has to
+ // check the sysprop every time bouncer is about to be shown.
+ if (Flags.bouncerUiRevamp() && (ActivityManager.isRunningInUserTestHarness()
+ || ActivityManager.isRunningInTestHarness())) {
+ handleBlurSupportedChanged(!WindowRootViewBlurRepository.isDisableBlurSysPropSet());
+ }
}
@Override
@@ -552,6 +585,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mSceneTransitionCollectionJob.cancel(null);
mSceneTransitionCollectionJob = null;
}
+
+ if (mBlurEnabledCollectionJob != null) {
+ mBlurEnabledCollectionJob.cancel(null);
+ mBlurEnabledCollectionJob = null;
+ }
}
/** */
@@ -718,6 +756,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
if (bouncerUserSwitcher != null) {
bouncerUserSwitcher.setAlpha(0f);
}
+
+ refreshBouncerBackground();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a67ec65cceda..8734d05bc894 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -296,6 +296,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mGestureDetector =
new MagnificationGestureDetector(mContext, handler, this);
mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+ mWindowInsetChangeRunnable.run();
// Initialize listeners.
mMirrorViewRunnable = new Runnable() {
@@ -367,8 +368,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private boolean updateSystemGestureInsetsTop() {
final WindowMetrics windowMetrics = mWm.getCurrentWindowMetrics();
final Insets insets = windowMetrics.getWindowInsets().getInsets(systemGestures());
- final int gestureTop =
- insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
+ final int gestureTop;
+ if (Flags.updateWindowMagnifierBottomBoundary()) {
+ gestureTop = windowMetrics.getBounds().bottom - insets.bottom;
+ } else {
+ gestureTop = insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
+ }
if (gestureTop != mSystemGestureTop) {
mSystemGestureTop = gestureTop;
return true;
@@ -953,7 +958,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
? mSystemGestureTop - height + mOuterBorderSize
: mWindowBounds.bottom - height + mOuterBorderSize;
final int y = MathUtils.clamp(mMagnificationFrame.top - mMirrorSurfaceMargin, minY, maxY);
-
if (computeWindowSize) {
LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
params.width = width;
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 9a30c213a2f9..fcf51051940c 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -46,7 +46,10 @@ import java.io.PrintWriter;
import javax.inject.Inject;
-/** Controller for {@link BatteryMeterView}. **/
+/**
+ * Controller for {@link BatteryMeterView}.
+ * @deprecated once [NewStatusBarIcons] is rolled out, this class is no longer needed
+ */
public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index f01a6dbf568f..ff741625a3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -104,6 +104,7 @@ interface CommunalModule {
companion object {
const val LOGGABLE_PREFIXES = "loggable_prefixes"
const val LAUNCHER_PACKAGE = "launcher_package"
+ const val SWIPE_TO_HUB = "swipe_to_hub"
@Provides
@Communal
@@ -143,5 +144,11 @@ interface CommunalModule {
fun provideLauncherPackage(@Main resources: Resources): String {
return resources.getString(R.string.launcher_overlayable_package)
}
+
+ @Provides
+ @Named(SWIPE_TO_HUB)
+ fun provideSwipeToHub(@Main resources: Resources): Boolean {
+ return resources.getBoolean(R.bool.config_swipeToOpenGlanceableHub)
+ }
}
}
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 49003a735fbd..a4860dfc47ce 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
@@ -202,6 +202,12 @@ abstract class BaseCommunalViewModel(
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called as the user requests to switch to the previous player in UMO. */
+ open fun onShowPreviousMedia() {}
+
+ /** Called as the user requests to switch to the next player in UMO. */
+ open fun onShowNextMedia() {}
+
/** Called as the UI determines that a new widget has been added to the grid. */
open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
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 4bc44005d2fc..2169881d11c5 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
@@ -19,6 +19,7 @@ package com.android.systemui.communal.ui.viewmodel
import android.content.ComponentName
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SWIPE_TO_HUB
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -92,6 +93,7 @@ constructor(
private val metricsLogger: CommunalMetricsLogger,
mediaCarouselController: MediaCarouselController,
blurConfig: BlurConfig,
+ @Named(SWIPE_TO_HUB) private val swipeToHub: Boolean,
) :
BaseCommunalViewModel(
communalSceneInteractor,
@@ -254,6 +256,14 @@ constructor(
}
}
+ override fun onShowPreviousMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(-1)
+ }
+
+ override fun onShowNextMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(1)
+ }
+
override fun onTapWidget(componentName: ComponentName, rank: Int) {
metricsLogger.logTapWidget(componentName.flattenToString(), rank)
}
@@ -349,6 +359,8 @@ constructor(
/** See [CommunalSettingsInteractor.isV2FlagEnabled] */
fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
+ fun swipeToHubEnabled(): Boolean = swipeToHub
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3c68e3a09f02..8bff090959ab 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -74,6 +74,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
+import com.android.systemui.statusbar.notification.dagger.NotificationStackModule;
import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
import com.android.systemui.statusbar.notification.headsup.HeadsUpModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -169,6 +170,7 @@ import javax.inject.Named;
WallpaperModule.class,
ShortcutHelperModule.class,
ContextualEducationModule.class,
+ NotificationStackModule.class,
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index f85a23c1f091..eb96c921c181 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -24,6 +24,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -57,6 +58,7 @@ constructor(
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryInteractor: DeviceEntryInteractor,
@@ -116,6 +118,17 @@ constructor(
}
}
+ @SuppressLint("MissingPermission")
+ private fun shouldTransitionToCommunal(
+ shouldShowCommunal: Boolean,
+ isCommunalAvailable: Boolean,
+ ) =
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ shouldShowCommunal
+ } else {
+ isCommunalAvailable && dreamManager.canStartDreaming(false)
+ }
+
@OptIn(FlowPreview::class)
@SuppressLint("MissingPermission")
private fun listenForDozingToDreaming() {
@@ -141,9 +154,10 @@ constructor(
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
communalInteractor.isCommunalAvailable,
+ communalInteractor.shouldShowCommunal,
communalSceneInteractor.isIdleOnCommunal,
)
- .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
+ .collect { (_, isCommunalAvailable, shouldShowCommunal, isIdleOnCommunal) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
@@ -177,11 +191,9 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
- // Using false for isScreenOn as canStartDreaming returns false if any
- // dream, including doze, is active.
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
+ } else if (
+ shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ ) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
@@ -203,6 +215,7 @@ constructor(
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
+ communalInteractor.shouldShowCommunal,
communalInteractor.isCommunalAvailable,
communalSceneInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
@@ -212,6 +225,7 @@ constructor(
.collect {
(
_,
+ shouldShowCommunal,
isCommunalAvailable,
isIdleOnCommunal,
biometricUnlockState,
@@ -245,7 +259,9 @@ constructor(
ownerReason = "waking from dozing",
)
}
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ } else if (
+ shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ ) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 251af11f7fe6..c1c509b8fd57 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -129,20 +129,37 @@ constructor(
if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
if (SceneContainerFlag.isEnabled) return
scope.launch {
- powerInteractor.isAwake
- .debounce(50L)
- .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(communalInteractor.isCommunalAvailable)
- .collect { isCommunalAvailable ->
- if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
- communalSceneInteractor.snapToScene(
- newScene = CommunalScenes.Communal,
- loggingReason = "from dreaming to hub",
- )
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.shouldShowCommunal)
+ .collect { shouldShowCommunal ->
+ if (shouldShowCommunal) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
}
- }
+ } else {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.isCommunalAvailable)
+ .collect { isCommunalAvailable ->
+ if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index d63c2e07b94f..0107a5278e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -23,11 +23,11 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
+import androidx.annotation.VisibleForTesting
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.tracing.TraceStateLogger
-import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
@@ -38,9 +38,10 @@ import com.android.systemui.res.R
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimator
+import kotlin.math.sign
private const val FLING_SLOP = 1000000
-private const val DISMISS_DELAY = 100L
+@VisibleForTesting const val DISMISS_DELAY = 100L
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
@@ -64,7 +65,7 @@ class MediaCarouselScrollHandler(
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingManager: FalsingManager,
private val logSmartspaceImpression: (Boolean) -> Unit,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
) {
/** Trace state logger for media carousel visibility */
private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
@@ -96,7 +97,7 @@ class MediaCarouselScrollHandler(
/** What's the currently visible player index? */
var visibleMediaIndex: Int = 0
- private set
+ @VisibleForTesting set
/** How much are we scrolled into the current media? */
private var scrollIntoCurrentMedia: Int = 0
@@ -137,14 +138,14 @@ class MediaCarouselScrollHandler(
eStart: MotionEvent?,
eCurrent: MotionEvent,
vX: Float,
- vY: Float
+ vY: Float,
) = onFling(vX, vY)
override fun onScroll(
down: MotionEvent?,
lastMotion: MotionEvent,
distanceX: Float,
- distanceY: Float
+ distanceY: Float,
) = onScroll(down!!, lastMotion, distanceX)
override fun onDown(e: MotionEvent): Boolean {
@@ -157,6 +158,7 @@ class MediaCarouselScrollHandler(
val touchListener =
object : Gefingerpoken {
override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+
override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
}
@@ -168,7 +170,7 @@ class MediaCarouselScrollHandler(
scrollX: Int,
scrollY: Int,
oldScrollX: Int,
- oldScrollY: Int
+ oldScrollY: Int,
) {
if (playerWidthPlusPadding == 0) {
return
@@ -177,7 +179,7 @@ class MediaCarouselScrollHandler(
val relativeScrollX = scrollView.relativeScrollX
onMediaScrollingChanged(
relativeScrollX / playerWidthPlusPadding,
- relativeScrollX % playerWidthPlusPadding
+ relativeScrollX % playerWidthPlusPadding,
)
}
}
@@ -209,7 +211,7 @@ class MediaCarouselScrollHandler(
0,
carouselWidth,
carouselHeight,
- cornerRadius.toFloat()
+ cornerRadius.toFloat(),
)
}
}
@@ -235,7 +237,7 @@ class MediaCarouselScrollHandler(
getMaxTranslation().toFloat(),
0.0f,
1.0f,
- Math.abs(contentTranslation)
+ Math.abs(contentTranslation),
)
val settingsTranslation =
(1.0f - settingsOffset) *
@@ -323,7 +325,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -391,7 +393,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
} else {
@@ -430,7 +432,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = vX,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -583,10 +585,35 @@ class MediaCarouselScrollHandler(
// We need to post this to wait for the active player becomes visible.
mainExecutor.executeDelayed(
{ scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
- SCROLL_DELAY
+ SCROLL_DELAY,
)
}
+ /**
+ * Scrolls the media carousel by the number of players specified by [step]. If scrolling beyond
+ * the carousel's bounds:
+ * - If the carousel is not dismissible, the settings button is displayed.
+ * - If the carousel is dismissible, no action taken.
+ *
+ * @param step A positive number means next, and negative means previous.
+ */
+ fun scrollByStep(step: Int) {
+ val destIndex = visibleMediaIndex + step
+ if (destIndex >= mediaContent.childCount || destIndex < 0) {
+ if (!showsSettingsButton) return
+ var translation = getMaxTranslation() * sign(-step.toFloat())
+ translation = if (isRtl) -translation else translation
+ PhysicsAnimator.getInstance(this)
+ .spring(CONTENT_TRANSLATION, translation, config = translationConfig)
+ .start()
+ scrollView.animationTargetX = translation
+ } else if (scrollView.getContentTranslation() != 0.0f) {
+ resetTranslation(true)
+ } else {
+ scrollToPlayer(destIndex = destIndex)
+ }
+ }
+
companion object {
private val CONTENT_TRANSLATION =
object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 16dff7d11002..11b014c2147f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -28,6 +28,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -83,34 +84,78 @@ constructor(
.flowOn(backgroundDispatcher)
/** Sets for the current user the set of [TileSpec] to display as large tiles. */
- fun setLargeTilesSpecs(specs: Set<TileSpec>) {
- setLargeTilesSpecsForUser(specs, userRepository.getSelectedUserInfo().id)
+ fun writeLargeTileSpecs(specs: Set<TileSpec>) {
+ with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
+ writeLargeTileSpecs(specs)
+ setLargeTilesDefault(false)
+ }
}
- private fun setLargeTilesSpecsForUser(specs: Set<TileSpec>, userId: Int) {
- with(getSharedPrefs(userId)) {
- edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ suspend fun deleteLargeTileDataJob() {
+ userRepository.selectedUserInfo.collect { userInfo ->
+ getSharedPrefs(userInfo.id)
+ .edit()
+ .remove(LARGE_TILES_SPECS_KEY)
+ .remove(LARGE_TILES_DEFAULT_KEY)
+ .apply()
}
}
+ private fun SharedPreferences.writeLargeTileSpecs(specs: Set<TileSpec>) {
+ edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ }
+
/**
- * Sets the initial tiles as large, if there is no set in SharedPrefs for the [userId]. This is
- * to be used when upgrading to a build that supports large/small tiles.
+ * Sets the initial set of large tiles. One of the following cases will happen:
+ * * If we are setting the default set (no value stored in settings for the list of tiles), set
+ * the large tiles based on [defaultLargeTilesRepository]. We do this to signal future reboots
+ * that we have performed the upgrade path once. In this case, we will mark that we set them
+ * as the default in case a restore needs to modify them later.
+ * * If we got a list of tiles restored from a device and nothing has modified the list of
+ * tiles, set all the restored tiles to large. Note that if we also restored a set of large
+ * tiles before this was called, [LARGE_TILES_DEFAULT_KEY] will be false and we won't
+ * overwrite it.
+ * * If we got a list of tiles from settings, we consider that we upgraded in place and then we
+ * will set all those tiles to large IF there's no current set of large tiles.
*
* Even if largeTilesSpec is read Eagerly before we know if we are in an initial state, because
* we are not writing the default values to the SharedPreferences, the file will not contain the
* key and this call will succeed, as long as there hasn't been any calls to setLargeTilesSpecs
* for that user before.
*/
- fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, userId: Int) {
+ fun setInitialOrUpgradeLargeTiles(upgradePath: TilesUpgradePath, userId: Int) {
with(getSharedPrefs(userId)) {
- if (!contains(LARGE_TILES_SPECS_KEY)) {
- logger.i("Setting upgraded large tiles for user $userId: $specs")
- setLargeTilesSpecsForUser(specs, userId)
+ when (upgradePath) {
+ is TilesUpgradePath.DefaultSet -> {
+ writeLargeTileSpecs(defaultLargeTilesRepository.defaultLargeTiles)
+ logger.i("Large tiles set to default on init")
+ setLargeTilesDefault(true)
+ }
+ is TilesUpgradePath.RestoreFromBackup -> {
+ if (
+ getBoolean(LARGE_TILES_DEFAULT_KEY, false) ||
+ !contains(LARGE_TILES_SPECS_KEY)
+ ) {
+ writeLargeTileSpecs(upgradePath.value)
+ logger.i("Tiles restored from backup set to large: ${upgradePath.value}")
+ setLargeTilesDefault(false)
+ }
+ }
+ is TilesUpgradePath.ReadFromSettings -> {
+ if (!contains(LARGE_TILES_SPECS_KEY)) {
+ writeLargeTileSpecs(upgradePath.value)
+ logger.i("Tiles read from settings set to large: ${upgradePath.value}")
+ setLargeTilesDefault(false)
+ }
+ }
}
}
}
+ private fun SharedPreferences.setLargeTilesDefault(value: Boolean) {
+ edit().putBoolean(LARGE_TILES_DEFAULT_KEY, value).apply()
+ }
+
private fun getSharedPrefs(userId: Int): SharedPreferences {
return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
}
@@ -118,6 +163,7 @@ constructor(
companion object {
private const val TAG = "QSPreferencesRepository"
private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+ private const val LARGE_TILES_DEFAULT_KEY = "large_tiles_default"
const val FILE_NAME = "quick_settings_prefs"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
index 86838b438bc6..9b98797ef393 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,10 +28,20 @@ class QSPreferencesInteractor @Inject constructor(private val repo: QSPreference
val largeTilesSpecs: Flow<Set<TileSpec>> = repo.largeTilesSpecs
fun setLargeTilesSpecs(specs: Set<TileSpec>) {
- repo.setLargeTilesSpecs(specs)
+ repo.writeLargeTileSpecs(specs)
}
- fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, user: Int) {
- repo.setInitialLargeTilesSpecs(specs, user)
+ /**
+ * This method should be called to indicate that a "new" set of tiles has been determined for a
+ * particular user coming from different upgrade sources.
+ *
+ * @see TilesUpgradePath for more information
+ */
+ fun setInitialOrUpgradeLargeTilesSpecs(specs: TilesUpgradePath, user: Int) {
+ repo.setInitialOrUpgradeLargeTiles(specs, user)
+ }
+
+ suspend fun deleteLargeTilesDataJob() {
+ repo.deleteLargeTileDataJob()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
index a8ac5c34d8f9..e2797356fa96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
@@ -19,11 +19,13 @@ package com.android.systemui.qs.panels.domain.startable
import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.panels.domain.interactor.QSPreferencesInteractor
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
class QSPanelsCoreStartable
@Inject
@@ -33,10 +35,14 @@ constructor(
@Background private val backgroundApplicationScope: CoroutineScope,
) : CoreStartable {
override fun start() {
- backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
- tileSpecRepository.tilesReadFromSetting.receiveAsFlow().collect { (tiles, userId) ->
- preferenceInteractor.setInitialLargeTilesSpecs(tiles, userId)
+ if (QsInCompose.isEnabled) {
+ backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
+ tileSpecRepository.tilesUpgradePath.receiveAsFlow().collect { (tiles, userId) ->
+ preferenceInteractor.setInitialOrUpgradeLargeTilesSpecs(tiles, userId)
+ }
}
+ } else {
+ backgroundApplicationScope.launch { preferenceInteractor.deleteLargeTilesDataJob() }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 6b7dd386bb46..c50d5dad10c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -24,6 +24,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.RetailModeRepository
@@ -78,7 +79,7 @@ interface TileSpecRepository {
/** Reset the current set of tiles to the default list of tiles */
suspend fun resetToDefault(userId: Int)
- val tilesReadFromSetting: ReceiveChannel<Pair<Set<TileSpec>, Int>>
+ val tilesUpgradePath: ReceiveChannel<Pair<TilesUpgradePath, Int>>
companion object {
/** Position to indicate the end of the list */
@@ -112,8 +113,8 @@ constructor(
.filter { it !is TileSpec.Invalid }
}
- private val _tilesReadFromSetting = Channel<Pair<Set<TileSpec>, Int>>(capacity = 5)
- override val tilesReadFromSetting = _tilesReadFromSetting
+ private val _tilesUpgradePath = Channel<Pair<TilesUpgradePath, Int>>(capacity = 5)
+ override val tilesUpgradePath = _tilesUpgradePath
private val userTileRepositories = SparseArray<UserTileSpecRepository>()
@@ -122,8 +123,8 @@ constructor(
val userTileRepository = userTileSpecRepositoryFactory.create(userId)
userTileRepositories.put(userId, userTileRepository)
applicationScope.launchTraced("TileSpecRepository.aggregateTilesPerUser") {
- for (tilesFromSettings in userTileRepository.tilesReadFromSettings) {
- _tilesReadFromSetting.send(tilesFromSettings to userId)
+ for (tileUpgrade in userTileRepository.tilesUpgradePath) {
+ _tilesUpgradePath.send(tileUpgrade to userId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 7b56cd92a081..5aa5edaa726e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -9,6 +9,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.SecureSettings
import dagger.assisted.Assisted
@@ -49,8 +50,8 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- private val _tilesReadFromSettings = Channel<Set<TileSpec>>(capacity = 2)
- val tilesReadFromSettings: ReceiveChannel<Set<TileSpec>> = _tilesReadFromSettings
+ private val _tilesUpgradePath = Channel<TilesUpgradePath>(capacity = 3)
+ val tilesUpgradePath: ReceiveChannel<TilesUpgradePath> = _tilesUpgradePath
private val defaultTiles: List<TileSpec>
get() = defaultTilesRepository.defaultTiles
@@ -67,14 +68,23 @@ constructor(
.scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
change
.apply(current)
- .also {
- if (current != it) {
+ .also { afterRestore ->
+ if (current != afterRestore) {
if (change is RestoreTiles) {
- logger.logTilesRestoredAndReconciled(current, it, userId)
+ logger.logTilesRestoredAndReconciled(
+ current,
+ afterRestore,
+ userId,
+ )
} else {
- logger.logProcessTileChange(change, it, userId)
+ logger.logProcessTileChange(change, afterRestore, userId)
}
}
+ if (change is RestoreTiles) {
+ _tilesUpgradePath.send(
+ TilesUpgradePath.RestoreFromBackup(afterRestore.toSet())
+ )
+ }
}
// Distinct preserves the order of the elements removing later
// duplicates,
@@ -154,7 +164,9 @@ constructor(
private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> {
val loadedTiles = loadTilesFromSettings(userId)
if (loadedTiles.isNotEmpty()) {
- _tilesReadFromSettings.send(loadedTiles.toSet())
+ _tilesUpgradePath.send(TilesUpgradePath.ReadFromSettings(loadedTiles.toSet()))
+ } else {
+ _tilesUpgradePath.send(TilesUpgradePath.DefaultSet)
}
return parseTileSpecs(loadedTiles, userId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
new file mode 100644
index 000000000000..98f30c22d0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 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.qs.pipeline.shared
+
+/** Upgrade paths indicating the source of the list of QS tiles. */
+sealed interface TilesUpgradePath {
+
+ sealed interface UpgradeWithTiles : TilesUpgradePath {
+ val value: Set<TileSpec>
+ }
+
+ /** This indicates a set of tiles that was read from Settings on user start */
+ @JvmInline value class ReadFromSettings(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+ /** This indicates a set of tiles that was restored from backup */
+ @JvmInline value class RestoreFromBackup(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+ /**
+ * This indicates that no tiles were read from Settings on user start so the default has been
+ * stored.
+ */
+ data object DefaultSet : TilesUpgradePath
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index a379ef7b0b96..305e71e48702 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -520,7 +520,10 @@ constructor(
val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled()
if (
!hubShowing &&
- (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
+ (touchOnNotifications ||
+ touchOnUmo ||
+ touchOnSmartspace ||
+ !communalViewModel.swipeToHubEnabled())
) {
logger.d({
"Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 9a79e1a45505..ce48c85d57ae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -31,16 +31,26 @@ import android.os.Trace.TRACE_TAG_APP
import android.provider.AlarmClock
import android.view.DisplayCutout
import android.view.View
+import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.TextView
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.dp
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.doOnLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterView.MODE_ESTIMATE
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
@@ -60,12 +70,15 @@ import com.android.systemui.shade.carrier.ShadeCarrierGroup
import com.android.systemui.shade.carrier.ShadeCarrierGroupController
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryWithEstimate
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -76,6 +89,7 @@ import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.flow.MutableStateFlow
/**
* Controller for QS header.
@@ -100,6 +114,7 @@ constructor(
private val shadeDisplaysRepositoryLazy: Lazy<ShadeDisplaysRepository>,
private val variableDateViewControllerFactory: VariableDateViewController.Factory,
@Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
+ private val batteryViewModelFactory: BatteryViewModel.Factory,
private val dumpManager: DumpManager,
private val shadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder,
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
@@ -162,6 +177,8 @@ constructor(
private var lastInsets: WindowInsets? = null
private var nextAlarmIntent: PendingIntent? = null
+ private val showBatteryEstimate = MutableStateFlow(false)
+
private var qsDisabled = false
private var visible = false
set(value) {
@@ -323,10 +340,6 @@ constructor(
override fun onInit() {
variableDateViewControllerFactory.create(date as VariableDateView).init()
- batteryMeterViewController.init()
-
- // battery settings same as in QS icons
- batteryMeterViewController.ignoreTunerUpdates()
val fgColor =
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
@@ -336,11 +349,36 @@ constructor(
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(fgColor, bgColor)
- batteryIcon.updateColors(
- fgColor /* foreground */,
- bgColor /* background */,
- fgColor, /* single tone (current default) */
- )
+ if (!NewStatusBarIcons.isEnabled) {
+ batteryMeterViewController.init()
+
+ // battery settings same as in QS icons
+ batteryMeterViewController.ignoreTunerUpdates()
+
+ batteryIcon.isVisible = true
+ batteryIcon.updateColors(
+ fgColor /* foreground */,
+ bgColor /* background */,
+ fgColor, /* single tone (current default) */
+ )
+ } else {
+ // Configure the compose battery view
+ val batteryComposeView =
+ ComposeView(mView.context).apply {
+ setContent {
+ val showBatteryEstimate by showBatteryEstimate.collectAsStateWithLifecycle()
+ BatteryWithEstimate(
+ modifier = Modifier.height(17.dp).wrapContentWidth(),
+ viewModelFactory = batteryViewModelFactory,
+ isDark = { true },
+ showEstimate = showBatteryEstimate,
+ )
+ }
+ }
+ mView.requireViewById<ViewGroup>(R.id.hover_system_icons_container).apply {
+ addView(batteryComposeView, -1)
+ }
+ }
carrierIconSlots =
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
@@ -474,7 +512,11 @@ constructor(
private fun updateBatteryMode() {
qsBatteryModeController.getBatteryMode(cutout, qsExpandedFraction)?.let {
- batteryIcon.setPercentShowMode(it)
+ if (NewStatusBarIcons.isEnabled) {
+ showBatteryEstimate.value = it == MODE_ESTIMATE
+ } else {
+ batteryIcon.setPercentShowMode(it)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index b9df9f868dc3..7d4b0ed6304c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -45,13 +45,17 @@ import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.domain.interactor.ShadeModeInteractorImpl
+import com.android.systemui.window.dagger.WindowRootViewBlurModule
import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
+@Module(
+ includes =
+ [StartShadeModule::class, ShadeViewProviderModule::class, WindowRootViewBlurModule::class]
+)
abstract class ShadeModule {
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 25ebc8c1ffa1..f06565f1b6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,8 +24,10 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_
import static android.os.Flags.allowPrivateProfile;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -44,6 +46,7 @@ import android.database.ContentObserver;
import android.database.ExecutorContentObserver;
import android.net.Uri;
import android.os.Looper;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -118,6 +121,11 @@ public class NotificationLockscreenUserManagerImpl implements
Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
private static final Uri SHOW_PRIVATE_LOCKSCREEN =
Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ private static final Uri REDACT_OTP_ON_WIFI =
+ Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI);
+
+ private static final Uri REDACT_OTP_IMMEDIATELY =
+ Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY);
private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
TimeUnit.MINUTES.toMillis(10);
@@ -307,6 +315,9 @@ public class NotificationLockscreenUserManagerImpl implements
@VisibleForTesting
protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false);
+ protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true);
+ protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false);
+
protected int mCurrentUserId = 0;
protected NotificationPresenter mPresenter;
@@ -363,6 +374,8 @@ public class NotificationLockscreenUserManagerImpl implements
mLockScreenUris.add(SHOW_LOCKSCREEN);
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
+ mLockScreenUris.add(REDACT_OTP_ON_WIFI);
+ mLockScreenUris.add(REDACT_OTP_IMMEDIATELY);
dumpManager.registerDumpable(this);
@@ -432,6 +445,10 @@ public class NotificationLockscreenUserManagerImpl implements
changed |= updateUserShowSettings(user.getIdentifier());
} else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
changed |= updateUserShowPrivateSettings(user.getIdentifier());
+ } else if (REDACT_OTP_ON_WIFI.equals(uri)) {
+ changed |= updateRedactOtpOnWifiSetting();
+ } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) {
+ changed |= updateRedactOtpImmediatelySetting();
}
}
@@ -465,6 +482,14 @@ public class NotificationLockscreenUserManagerImpl implements
true,
mLockscreenSettingsObserver,
USER_ALL);
+ mSecureSettings.registerContentObserverAsync(
+ REDACT_OTP_ON_WIFI,
+ mLockscreenSettingsObserver
+ );
+ mSecureSettings.registerContentObserverAsync(
+ REDACT_OTP_IMMEDIATELY,
+ mLockscreenSettingsObserver
+ );
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
@@ -602,6 +627,28 @@ public class NotificationLockscreenUserManagerImpl implements
}
@WorkerThread
+ private boolean updateRedactOtpOnWifiSetting() {
+ boolean originalValue = mRedactOtpOnWifi.get();
+ boolean newValue = mSecureSettings.getIntForUser(
+ REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
+ 0,
+ Process.myUserHandle().getIdentifier()) != 0;
+ mRedactOtpOnWifi.set(newValue);
+ return originalValue != newValue;
+ }
+
+ @WorkerThread
+ private boolean updateRedactOtpImmediatelySetting() {
+ boolean originalValue = mRedactOtpImmediately.get();
+ boolean newValue = mSecureSettings.getIntForUser(
+ REDACT_OTP_NOTIFICATION_IMMEDIATELY,
+ 0,
+ Process.myUserHandle().getIdentifier()) != 0;
+ mRedactOtpImmediately.set(newValue);
+ return originalValue != newValue;
+ }
+
+ @WorkerThread
private boolean updateGlobalKeyguardSettings() {
final boolean oldValue = mKeyguardAllowingNotifications;
mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
@@ -769,23 +816,31 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
- if (mConnectedToWifi.get()) {
- return false;
+ if (!mRedactOtpOnWifi.get()) {
+ if (mConnectedToWifi.get()) {
+ return false;
+ }
+
+ long lastWifiConnectTime = mLastWifiConnectionTime.get();
+ // If the device has connected to wifi since receiving the notification, do not redact
+ if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
+ return false;
+ }
}
if (ent.getRanking() == null || !ent.getRanking().hasSensitiveContent()) {
return false;
}
- long lastWifiConnectTime = mLastWifiConnectionTime.get();
- // If the device has connected to wifi since receiving the notification, do not redact
- if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
- return false;
+ long latestTimeForRedaction;
+ if (mRedactOtpImmediately.get()) {
+ latestTimeForRedaction = mLastLockTime.get();
+ } else {
+ // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
+ // when this notification arrived, do not redact
+ latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
}
- // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS when
- // this notification arrived, do not redact
- long latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index a2c0226addfa..f466278e15a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -32,9 +32,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
-import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
@@ -86,12 +84,7 @@ constructor(
OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
}
- val colors =
- if (StatusBarNotifChips.isEnabled && state.promotedContent != null) {
- state.promotedContent.toCustomColorsModel()
- } else {
- ColorsModel.Themed
- }
+ val colors = ColorsModel.AccentThemed
// This block mimics OngoingCallController#updateChip.
if (state.startTimeMs <= 0L) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 8357df42937e..2d6102e310f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -27,7 +27,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -85,8 +85,7 @@ constructor(
contentDescription,
)
}
- val colors = this.promotedContent.toCustomColorsModel()
-
+ val colors = ColorsModel.SystemThemed
val clickListener: () -> Unit = {
// The notification pipeline needs everything to run on the main thread, so keep
// this event on the main thread.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 456cd121a540..d41353b2c176 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.ui.binder
import android.annotation.IdRes
+import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.view.View
@@ -32,6 +33,7 @@ import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
@@ -76,8 +78,10 @@ object OngoingActivityChipBinder {
chipTimeView.setTextColor(textColor)
chipTextView.setTextColor(textColor)
chipShortTimeDeltaView.setTextColor(textColor)
- (chipBackgroundView.background as GradientDrawable).color =
- chipModel.colors.background(chipContext)
+ (chipBackgroundView.background as GradientDrawable).setBackgroundColors(
+ chipModel.colors,
+ chipContext,
+ )
}
is OngoingActivityChipModel.Inactive -> {
// The Chronometer should be stopped to prevent leaks -- see b/192243808 and
@@ -460,5 +464,20 @@ object OngoingActivityChipBinder {
chipView.minimumWidth = minimumWidth
}
+ private fun GradientDrawable.setBackgroundColors(colors: ColorsModel, context: Context) {
+ this.color = colors.background(context)
+ val outline = colors.outline(context)
+ if (outline != null) {
+ this.setStroke(
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_outline_width
+ ),
+ outline,
+ )
+ } else {
+ this.setStroke(0, /* color= */ 0)
+ }
+ }
+
@IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 32de0fbfd870..8443d106dfb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -20,16 +20,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
@@ -37,6 +30,8 @@ import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.TextMeasurer
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
@@ -83,15 +78,14 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
softWrap = false,
modifier =
modifier
- .customTextContentLayout(
+ .hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
maxTextWidth = maxTextWidth,
startPadding = startPadding,
endPadding = endPadding,
- ) { constraintWidth ->
- val intrinsicWidth =
- textMeasurer.measure(text, textStyle, softWrap = false).size.width
- intrinsicWidth <= constraintWidth
- }
+ )
.neverDecreaseWidth(),
)
}
@@ -108,7 +102,6 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
}
is OngoingActivityChipModel.Active.Text -> {
- var hasOverflow by remember { mutableStateOf(false) }
val text = viewModel.text
Text(
text = text,
@@ -116,24 +109,14 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
style = textStyle,
softWrap = false,
modifier =
- modifier
- .customTextContentLayout(
- maxTextWidth = maxTextWidth,
- startPadding = startPadding,
- endPadding = endPadding,
- ) { constraintWidth ->
- val intrinsicWidth =
- textMeasurer.measure(text, textStyle, softWrap = false).size.width
- hasOverflow = intrinsicWidth > constraintWidth
- constraintWidth.toFloat() / intrinsicWidth.toFloat() > 0.5f
- }
- .overflowFadeOut(
- hasOverflow = { hasOverflow },
- fadeLength =
- dimensionResource(
- id = R.dimen.ongoing_activity_chip_text_fading_edge_length
- ),
- ),
+ modifier.hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ ),
)
}
@@ -180,45 +163,67 @@ private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode {
}
/**
- * A custom layout modifier for text that ensures its text is only visible if a provided
- * [shouldShow] callback returns true. Imposes a provided [maxTextWidthPx]. Also, accounts for
- * provided padding values if provided and ensures its text is placed with the provided padding
- * included around it.
+ * A custom layout modifier for text that ensures the text is only visible if it completely fits
+ * within the constrained bounds. Imposes a provided [maxTextWidthPx]. Also, accounts for provided
+ * padding values if provided and ensures its text is placed with the provided padding included
+ * around it.
*/
-private fun Modifier.customTextContentLayout(
+private fun Modifier.hideTextIfDoesNotFit(
+ text: String,
+ textStyle: TextStyle,
+ textMeasurer: TextMeasurer,
maxTextWidth: Dp,
startPadding: Dp = 0.dp,
endPadding: Dp = 0.dp,
- shouldShow: (constraintWidth: Int) -> Boolean,
): Modifier {
return this.then(
- CustomTextContentLayoutElement(maxTextWidth, startPadding, endPadding, shouldShow)
+ HideTextIfDoesNotFitElement(
+ text,
+ textStyle,
+ textMeasurer,
+ maxTextWidth,
+ startPadding,
+ endPadding,
+ )
)
}
-private data class CustomTextContentLayoutElement(
+private data class HideTextIfDoesNotFitElement(
+ val text: String,
+ val textStyle: TextStyle,
+ val textMeasurer: TextMeasurer,
val maxTextWidth: Dp,
val startPadding: Dp,
val endPadding: Dp,
- val shouldShow: (constrainedWidth: Int) -> Boolean,
-) : ModifierNodeElement<CustomTextContentLayoutNode>() {
- override fun create(): CustomTextContentLayoutNode {
- return CustomTextContentLayoutNode(maxTextWidth, startPadding, endPadding, shouldShow)
+) : ModifierNodeElement<HideTextIfDoesNotFitNode>() {
+ override fun create(): HideTextIfDoesNotFitNode {
+ return HideTextIfDoesNotFitNode(
+ text,
+ textStyle,
+ textMeasurer,
+ maxTextWidth,
+ startPadding,
+ endPadding,
+ )
}
- override fun update(node: CustomTextContentLayoutNode) {
- node.shouldShow = shouldShow
+ override fun update(node: HideTextIfDoesNotFitNode) {
+ node.text = text
+ node.textStyle = textStyle
+ node.textMeasurer = textMeasurer
node.maxTextWidth = maxTextWidth
node.startPadding = startPadding
node.endPadding = endPadding
}
}
-private class CustomTextContentLayoutNode(
+private class HideTextIfDoesNotFitNode(
+ var text: String,
+ var textStyle: TextStyle,
+ var textMeasurer: TextMeasurer,
var maxTextWidth: Dp,
var startPadding: Dp,
var endPadding: Dp,
- var shouldShow: (constrainedWidth: Int) -> Boolean,
) : Modifier.Node(), LayoutModifierNode {
override fun MeasureScope.measure(
measurable: Measurable,
@@ -230,9 +235,10 @@ private class CustomTextContentLayoutNode(
.coerceAtLeast(constraints.minWidth)
val placeable = measurable.measure(constraints.copy(maxWidth = maxWidth))
- val height = placeable.height
- val width = placeable.width
- return if (shouldShow(maxWidth)) {
+ val intrinsicWidth = textMeasurer.measure(text, textStyle, softWrap = false).size.width
+ return if (intrinsicWidth <= maxWidth) {
+ val height = placeable.height
+ val width = placeable.width
layout(width + horizontalPadding.roundToPx(), height) {
placeable.place(startPadding.roundToPx(), 0)
}
@@ -241,20 +247,3 @@ private class CustomTextContentLayoutNode(
}
}
}
-
-private fun Modifier.overflowFadeOut(hasOverflow: () -> Boolean, fadeLength: Dp): Modifier {
- return graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen).drawWithCache {
- val width = size.width
- val start = (width - fadeLength.toPx()).coerceAtLeast(0f)
- val gradient =
- Brush.horizontalGradient(
- colors = listOf(Color.Black, Color.Transparent),
- startX = start,
- endX = width,
- )
- onDrawWithContent {
- drawContent()
- if (hasOverflow()) drawRect(brush = gradient, blendMode = BlendMode.DstIn)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 76c53861f0ab..1cdf6800fb97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.ui.compose
import android.content.res.ColorStateList
import android.view.ViewGroup
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -103,6 +104,13 @@ private fun ChipBody(
} else {
dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
}
+
+ val outline = model.colors.outline(context)
+ val outlineWidth = dimensionResource(R.dimen.ongoing_activity_chip_outline_width)
+
+ val shape =
+ RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))
+
// Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
// height of the chip is determined by the height of the background of the Row below.
Box(
@@ -121,12 +129,7 @@ private fun ChipBody(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier =
- Modifier.clip(
- RoundedCornerShape(
- dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
- )
- )
- .height(dimensionResource(R.dimen.ongoing_appops_chip_height))
+ Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
.thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
@@ -136,7 +139,14 @@ private fun ChipBody(
}
}
}
- .background(Color(model.colors.background(context).defaultColor))
+ .background(Color(model.colors.background(context).defaultColor), shape = shape)
+ .thenIf(outline != null) {
+ Modifier.border(
+ width = outlineWidth,
+ color = Color(outline!!),
+ shape = shape,
+ )
+ }
.padding(
horizontal =
if (hasEmbeddedIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 25f90f9a0065..4954cb0a1b24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -21,7 +21,6 @@ import android.content.res.ColorStateList
import androidx.annotation.ColorInt
import com.android.settingslib.Utils
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
/** Model representing how the chip in the status bar should be colored. */
sealed interface ColorsModel {
@@ -31,13 +30,38 @@ sealed interface ColorsModel {
/** The color for the text (and icon) on the chip. */
@ColorInt fun text(context: Context): Int
- /** The chip should match the theme's primary color. */
- data object Themed : ColorsModel {
+ /** The color to use for the chip outline, or null if the chip shouldn't have an outline. */
+ @ColorInt fun outline(context: Context): Int?
+
+ /** The chip should match the theme's primary accent color. */
+ // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
+ // only gets updated when a different configuration change happens, like a rotation.
+ data object AccentThemed : ColorsModel {
override fun background(context: Context): ColorStateList =
Utils.getColorAttr(context, com.android.internal.R.attr.colorAccent)
override fun text(context: Context) =
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+
+ override fun outline(context: Context) = null
+ }
+
+ /** The chip should match the system theme main color. */
+ // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
+ // only gets updated when a different configuration change happens, like a rotation.
+ data object SystemThemed : ColorsModel {
+ override fun background(context: Context): ColorStateList =
+ ColorStateList.valueOf(
+ context.getColor(com.android.internal.R.color.materialColorSurfaceDim)
+ )
+
+ override fun text(context: Context) =
+ context.getColor(com.android.internal.R.color.materialColorOnSurface)
+
+ override fun outline(context: Context) =
+ // Outline is required on the SystemThemed chip to guarantee the chip doesn't completely
+ // blend in with the background.
+ context.getColor(com.android.internal.R.color.materialColorOutlineVariant)
}
/** The chip should have the given background color and primary text color. */
@@ -46,6 +70,8 @@ sealed interface ColorsModel {
ColorStateList.valueOf(backgroundColorInt)
override fun text(context: Context): Int = primaryTextColorInt
+
+ override fun outline(context: Context) = null
}
/** The chip should have a red background with white text. */
@@ -55,15 +81,7 @@ sealed interface ColorsModel {
}
override fun text(context: Context) = context.getColor(android.R.color.white)
- }
- companion object {
- /** Converts the promoted notification colors to a [Custom] colors model. */
- fun PromotedNotificationContentModel.toCustomColorsModel(): Custom {
- return Custom(
- backgroundColorInt = this.colors.backgroundColor,
- primaryTextColorInt = this.colors.primaryTextColor,
- )
- }
+ override fun outline(context: Context) = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
index 52495eb55436..c19b144b7f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
@@ -51,9 +51,8 @@ class ChipTextTruncationHelper(private val view: View) {
}
/**
- * Returns true if this view should show the text because there's enough room for a substantial
- * amount of text, and returns false if this view should hide the text because the text is much
- * too long.
+ * Returns true if this view should show the text because there's enough room for all the text,
+ * and returns false if this view should hide the text because not all of it fits.
*
* @param desiredTextWidthPx should be calculated by having the view measure itself with
* [unlimitedWidthMeasureSpec] and then sending its `measuredWidth` to this method. (This
@@ -82,9 +81,8 @@ class ChipTextTruncationHelper(private val view: View) {
enforcedTextWidth = maxWidthBasedOnDimension
}
- // Only show the text if at least 50% of it can show. (Assume that if < 50% of the text will
- // be visible, the text will be more confusing than helpful.)
- return desiredTextWidthPx <= enforcedTextWidth * 2
+ // Only show the text if all of it can show
+ return desiredTextWidthPx <= enforcedTextWidth
}
private fun fetchMaxWidth() =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 3ba0ae3b3cb6..1a30caf0150b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -214,7 +214,6 @@ constructor(
if (
secondaryChip is InternalChipModel.Active &&
StatusBarNotifChips.isEnabled &&
- !StatusBarChipsModernization.isEnabled &&
!isScreenReasonablyLarge
) {
// If we have two showing chips and we don't have a ton of room
@@ -222,8 +221,10 @@ constructor(
// possible so that we have the highest chance of showing both chips (as
// opposed to showing the primary chip with a lot of text and completely
// hiding the secondary chip).
- // Also: If StatusBarChipsModernization is enabled, then we'll do the
- // squishing in Compose instead.
+ // TODO(b/392895330): If StatusBarChipsModernization is enabled, do the
+ // squishing in Compose instead, and be smart about it (e.g. if we have
+ // room for the first chip to show text and the second chip to be icon-only,
+ // do that instead of always squishing both chips.)
InternalMultipleOngoingActivityChipsModel(
primaryChip.squish(),
secondaryChip.squish(),
@@ -237,24 +238,31 @@ constructor(
/** Squishes the chip down to the smallest content possible. */
private fun InternalChipModel.Active.squish(): InternalChipModel.Active {
- return when (model) {
+ return if (model.shouldSquish()) {
+ InternalChipModel.Active(this.type, this.model.toIconOnly())
+ } else {
+ this
+ }
+ }
+
+ private fun OngoingActivityChipModel.Active.shouldSquish(): Boolean {
+ return when (this) {
// Icon-only is already maximum squished
- is OngoingActivityChipModel.Active.IconOnly -> this
+ is OngoingActivityChipModel.Active.IconOnly,
// Countdown shows just a single digit, so already maximum squished
- is OngoingActivityChipModel.Active.Countdown -> this
- // The other chips have icon+text, so we should hide the text
+ is OngoingActivityChipModel.Active.Countdown -> false
+ // The other chips have icon+text, so we can squish them by hiding text
is OngoingActivityChipModel.Active.Timer,
is OngoingActivityChipModel.Active.ShortTimeDelta,
- is OngoingActivityChipModel.Active.Text ->
- InternalChipModel.Active(this.type, this.model.toIconOnly())
+ is OngoingActivityChipModel.Active.Text -> true
}
}
private fun OngoingActivityChipModel.Active.toIconOnly(): OngoingActivityChipModel.Active {
// If this chip doesn't have an icon, then it only has text and we should continue showing
// its text. (This is theoretically impossible because
- // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon, but protect
- // against it just in case.)
+ // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon and
+ // [shouldSquish] returns false for that model, but protect against it just in case.)
val currentIcon = icon ?: return this
return OngoingActivityChipModel.Active.IconOnly(
key,
@@ -271,8 +279,38 @@ constructor(
*/
val chips: StateFlow<MultipleOngoingActivityChipsModel> =
if (StatusBarChipsModernization.isEnabled) {
- incomingChipBundle
- .map { bundle -> rankChips(bundle) }
+ combine(
+ incomingChipBundle.map { bundle -> rankChips(bundle) },
+ isScreenReasonablyLarge,
+ ) { rankedChips, isScreenReasonablyLarge ->
+ if (
+ StatusBarNotifChips.isEnabled &&
+ !isScreenReasonablyLarge &&
+ rankedChips.active.filter { !it.isHidden }.size >= 2
+ ) {
+ // If we have at least two showing chips and we don't have a ton of room
+ // (!isScreenReasonablyLarge), then we want to make both of them as small as
+ // possible so that we have the highest chance of showing both chips (as
+ // opposed to showing the first chip with a lot of text and completely
+ // hiding the other chips).
+ val squishedActiveChips =
+ rankedChips.active.map {
+ if (!it.isHidden && it.shouldSquish()) {
+ it.toIconOnly()
+ } else {
+ it
+ }
+ }
+
+ MultipleOngoingActivityChipsModel(
+ active = squishedActiveChips,
+ overflow = rankedChips.overflow,
+ inactive = rankedChips.inactive,
+ )
+ } else {
+ rankedChips
+ }
+ }
.stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel())
} else {
MutableStateFlow(MultipleOngoingActivityChipsModel()).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
index 6ceeb6aae7a5..bcaf1878a869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
@@ -26,7 +26,7 @@ import dagger.Module
* This is meant to be bound in SystemUI variants with [NotificationStackScrollLayoutController].
*/
@Module
-interface NotificationStackGoogleModule {
+interface NotificationStackModule {
@Binds
fun bindNotificationStackRebindingHider(
impl: NotificationStackRebindingHiderImpl
@@ -35,7 +35,7 @@ interface NotificationStackGoogleModule {
/** This is meant to be used by all SystemUI variants, also those without NSSL. */
@Module
-interface NotificationStackModule {
+interface NotificationStackOptionalModule {
@BindsOptionalOf
fun bindOptionalOfNotificationStackRebindingHider(): NotificationStackRebindingHider
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index e10825bc52fe..34f4969127e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -121,7 +121,7 @@ import javax.inject.Provider;
NotificationMemoryModule.class,
NotificationStatsLoggerModule.class,
NotificationsLogModule.class,
- NotificationStackModule.class,
+ NotificationStackOptionalModule.class,
})
public interface NotificationsModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index de113d365bd8..ccc2dffcfd7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -49,7 +49,7 @@ constructor(
) : Dumpable {
private val tag = "AvalancheController"
- private val debug = Compile.IS_DEBUG
+ private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
var baseEntryMapStr: () -> String = { "baseEntryMapStr not initialized" }
var enableAtRuntime = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 7c75983885ea..777ffda8c87d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -231,6 +231,7 @@ private class AODPromotedNotificationViewUpdater(root: View) {
) {
// Icon binding must be called in this order
updateImageView(icon, content.smallIcon)
+ icon?.setImageLevel(content.iconLevel)
icon?.setBackgroundColor(Background.colorInt)
icon?.originalIconColor = PrimaryText.colorInt
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index cd7872291801..39c7df064c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -96,6 +96,7 @@ constructor(
contentBuilder.wasPromotedAutomatically =
notification.extras.getBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false)
contentBuilder.smallIcon = notification.smallIconModel(imageModelProvider)
+ contentBuilder.iconLevel = notification.iconLevel
contentBuilder.appName = notification.loadHeaderAppName(context)
contentBuilder.subText = notification.subText()
contentBuilder.time = notification.extractWhen()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index af5a8203c979..38d41e37f916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -38,6 +38,7 @@ data class PromotedNotificationContentModel(
*/
val wasPromotedAutomatically: Boolean,
val smallIcon: ImageModel?,
+ val iconLevel: Int,
val appName: CharSequence?,
val subText: CharSequence?,
val shortCriticalText: String?,
@@ -67,6 +68,7 @@ data class PromotedNotificationContentModel(
class Builder(val key: String) {
var wasPromotedAutomatically: Boolean = false
var smallIcon: ImageModel? = null
+ var iconLevel: Int = 0
var appName: CharSequence? = null
var subText: CharSequence? = null
var time: When? = null
@@ -94,6 +96,7 @@ data class PromotedNotificationContentModel(
identity = Identity(key, style),
wasPromotedAutomatically = wasPromotedAutomatically,
smallIcon = smallIcon,
+ iconLevel = iconLevel,
appName = appName,
subText = subText,
shortCriticalText = shortCriticalText,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 58326dbb3a34..fa4fe46e690c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,6 +47,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
import com.android.systemui.statusbar.phone.ui.TintedIconManager;
@@ -79,7 +80,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
private TextView mCarrierLabel;
private ImageView mMultiUserAvatar;
- private BatteryMeterView mBatteryView;
+ @Nullable private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
private StatusBarUserSwitcherContainer mUserSwitcherContainer;
@@ -131,6 +132,11 @@ public class KeyguardStatusBarView extends RelativeLayout {
mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
+ if (NewStatusBarIcons.isEnabled()) {
+ // When this flag is rolled forward, this whole view can be removed
+ mBatteryView.setVisibility(View.GONE);
+ mBatteryView = null;
+ }
mCutoutSpace = findViewById(R.id.cutout_space_view);
mStatusIconArea = findViewById(R.id.status_icon_area);
mStatusIconContainer = findViewById(R.id.statusIcons);
@@ -259,7 +265,10 @@ public class KeyguardStatusBarView extends RelativeLayout {
mMultiUserAvatar.setVisibility(View.GONE);
}
}
- mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+
+ if (mBatteryView != null) {
+ mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+ }
}
private void updateSystemIconsLayoutParams() {
@@ -442,7 +451,9 @@ public class KeyguardStatusBarView extends RelativeLayout {
/** Should only be called from {@link KeyguardStatusBarViewController}. */
void onThemeChanged(TintedIconManager iconManager) {
- mBatteryView.setColorsFromContext(mContext);
+ if (mBatteryView != null) {
+ mBatteryView.setColorsFromContext(mContext);
+ }
updateIconsAndTextColors(iconManager);
}
@@ -450,7 +461,9 @@ public class KeyguardStatusBarView extends RelativeLayout {
void onOverlayChanged() {
final int carrierTheme = R.style.TextAppearance_StatusBar_Clock;
mCarrierLabel.setTextAppearance(carrierTheme);
- mBatteryView.updatePercentView();
+ if (mBatteryView != null) {
+ mBatteryView.updatePercentView();
+ }
final int userSwitcherTheme = R.style.TextAppearance_StatusBar_UserChip;
TextView userSwitcherName = mUserSwitcherContainer.findViewById(R.id.current_user_name);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 40245aef4f67..de7215461c80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -34,10 +34,12 @@ import android.provider.Settings;
import android.util.MathUtils;
import android.view.DisplayCutout;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.compose.ui.platform.ComposeView;
import androidx.core.animation.Animator;
import androidx.core.animation.AnimatorListenerAdapter;
import androidx.core.animation.ValueAnimator;
@@ -62,6 +64,7 @@ import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
import com.android.systemui.statusbar.disableflags.DisableStateTracker;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
@@ -71,10 +74,13 @@ import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventDefaultAnimator;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.phone.ui.TintedIconManager;
+import com.android.systemui.statusbar.pipeline.battery.ui.binder.UnifiedBatteryViewBinder;
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -125,6 +131,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final StatusBarIconController mStatusBarIconController;
private final TintedIconManager.Factory mTintedIconManagerFactory;
private final BatteryMeterViewController mBatteryMeterViewController;
+ private final BatteryViewModel.Factory mBatteryViewModelFactory;
private final ShadeViewStateProvider mShadeViewStateProvider;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -145,7 +152,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
- private View mSystemIconsContainer;
+ private ViewGroup mSystemIconsContainer;
private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
@@ -327,6 +334,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
StatusBarIconController statusBarIconController,
TintedIconManager.Factory tintedIconManagerFactory,
BatteryMeterViewController batteryMeterViewController,
+ BatteryViewModel.Factory batteryViewModelFactory,
ShadeViewStateProvider shadeViewStateProvider,
KeyguardStateController keyguardStateController,
KeyguardBypassController bypassController,
@@ -360,6 +368,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mStatusBarIconController = statusBarIconController;
mTintedIconManagerFactory = tintedIconManagerFactory;
mBatteryMeterViewController = batteryMeterViewController;
+ mBatteryViewModelFactory = batteryViewModelFactory;
mShadeViewStateProvider = shadeViewStateProvider;
mKeyguardStateController = keyguardStateController;
mKeyguardBypassController = bypassController;
@@ -417,7 +426,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
protected void onInit() {
super.onInit();
mCarrierTextController.init();
- mBatteryMeterViewController.init();
+ if (!NewStatusBarIcons.isEnabled()) {
+ mBatteryMeterViewController.init();
+ }
if (isMigrationEnabled()) {
KeyguardStatusBarViewBinder.bind(mView, mKeyguardStatusBarViewModel);
}
@@ -469,6 +480,15 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ if (NewStatusBarIcons.isEnabled()) {
+ ComposeView batteryComposeView = new ComposeView(mContext);
+ UnifiedBatteryViewBinder.bind(
+ batteryComposeView,
+ mBatteryViewModelFactory,
+ DarkIconInteractor.toIsAreaDark(mView.darkChangeFlow()));
+
+ mSystemIconsContainer.addView(batteryComposeView, -1);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 4d222fdb90ea..b2d337797b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -81,6 +81,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
+
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -226,7 +229,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
- private final float mDefaultScrimAlpha;
+ private float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
private float mPanelScrimMinFraction;
@@ -257,6 +260,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
private final BlurConfig mBlurConfig;
+ private final Lazy<WindowRootViewBlurInteractor> mWindowRootViewBlurInteractor;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -339,14 +343,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
KeyguardInteractor keyguardInteractor,
@Main CoroutineDispatcher mainDispatcher,
LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- BlurConfig blurConfig) {
+ BlurConfig blurConfig,
+ Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
mBlurConfig = blurConfig;
- // All scrims default alpha need to match bouncer background alpha to make sure the
- // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
- mDefaultScrimAlpha =
- Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
+ mWindowRootViewBlurInteractor = windowRootViewBlurInteractor;
+ mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -407,7 +410,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
- states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager, mBlurConfig);
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
}
@@ -485,6 +488,30 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
Edge.Companion.create(Scenes.Communal, LOCKSCREEN),
Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)),
mGlanceableHubConsumer, mMainDispatcher);
+
+ if (Flags.bouncerUiRevamp()) {
+ collectFlow(behindScrim,
+ mWindowRootViewBlurInteractor.get().isBlurCurrentlySupported(),
+ this::handleBlurSupportedChanged);
+ }
+ }
+
+ private void updateDefaultScrimAlpha(float alpha) {
+ mDefaultScrimAlpha = alpha;
+ for (ScrimState state : ScrimState.values()) {
+ state.setDefaultScrimAlpha(mDefaultScrimAlpha);
+ }
+ applyAndDispatchState();
+ }
+
+ private void handleBlurSupportedChanged(boolean isBlurSupported) {
+ if (isBlurSupported) {
+ updateDefaultScrimAlpha(TRANSPARENT_BOUNCER_SCRIM_ALPHA);
+ ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(mBlurConfig.getMaxBlurRadiusPx());
+ } else {
+ ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(0f);
+ updateDefaultScrimAlpha(BUSY_SCRIM_ALPHA);
+ }
}
// TODO(b/270984686) recompute scrim height accurately, based on shade contents.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 5f423cf35edd..071a57a8b298 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -17,14 +17,12 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
-import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
import android.graphics.Color;
import com.android.app.tracing.coroutines.TrackTracer;
import com.android.systemui.Flags;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.ui.ShadeColors;
@@ -116,8 +114,8 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
if (Flags.bouncerUiRevamp()) {
- mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
- mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+ mBehindAlpha = mDefaultScrimAlpha;
+ mNotifAlpha = 0f;
mBehindTint = mNotifTint = mSurfaceColor;
mFrontAlpha = 0f;
return;
@@ -153,12 +151,11 @@ public enum ScrimState {
if (previousState == SHADE_LOCKED) {
mBehindAlpha = previousState.getBehindAlpha();
mNotifAlpha = previousState.getNotifAlpha();
- mNotifBlurRadius = mBlurConfig.getMaxBlurRadiusPx();
} else {
mNotifAlpha = 0f;
mBehindAlpha = 0f;
}
- mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mFrontAlpha = mDefaultScrimAlpha;
mFrontTint = mSurfaceColor;
return;
}
@@ -403,7 +400,6 @@ public enum ScrimState {
DozeParameters mDozeParameters;
DockManager mDockManager;
boolean mDisplayRequiresBlanking;
- protected BlurConfig mBlurConfig;
boolean mLaunchingAffordanceWithPreview;
boolean mOccludeAnimationPlaying;
boolean mWakeLockScreenSensorActive;
@@ -417,7 +413,7 @@ public enum ScrimState {
protected float mNotifBlurRadius = 0.0f;
public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
- DockManager dockManager, BlurConfig blurConfig) {
+ DockManager dockManager) {
mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
@@ -425,7 +421,6 @@ public enum ScrimState {
mDozeParameters = dozeParameters;
mDockManager = dockManager;
mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
- mBlurConfig = blurConfig;
}
/** Prepare state for transition. */
@@ -536,4 +531,8 @@ public enum ScrimState {
public float getNotifBlurRadius() {
return mNotifBlurRadius;
}
+
+ public void setNotifBlurRadius(float value) {
+ mNotifBlurRadius = value;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index e33baf7c33ae..ded964d8a1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -57,11 +57,11 @@ import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.statusbar.CommandQueue;
@@ -76,11 +76,11 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wmshell.BubblesManager;
@@ -115,7 +115,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final static String TAG = "StatusBarNotificationActivityStarter";
private final Context mContext;
- private final int mDisplayId;
private final Handler mMainThreadHandler;
private final Executor mUiBgExecutor;
@@ -155,8 +154,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Inject
StatusBarNotificationActivityStarter(
- Context context,
- @DisplayId int displayId,
+ @ShadeDisplayAware Context context,
Handler mainThreadHandler,
@Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
@@ -189,7 +187,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
PowerInteractor powerInteractor,
UserTracker userTracker) {
mContext = context;
- mDisplayId = displayId;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
mVisibilityProvider = visibilityProvider;
@@ -493,6 +490,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean animate,
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry);
+ final int displayId = mContext.getDisplayId();
try {
ActivityTransitionAnimator.Controller animationController =
new StatusBarTransitionAnimatorController(
@@ -501,7 +499,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
isActivityIntent);
mActivityTransitionAnimator.startPendingIntentWithAnimation(
animationController,
@@ -511,11 +509,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
? getActivityOptions(
- mDisplayId,
+ displayId,
adapter,
mKeyguardStateController.isShowing(),
eventTime)
- : getActivityOptions(mDisplayId, adapter);
+ : getActivityOptions(displayId, adapter);
int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
mLogger.logSendPendingIntent(entry, intent, result);
@@ -533,6 +531,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
@NonNull ExpandableNotificationRow row) {
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
+ final int displayId = mContext.getDisplayId();
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -544,7 +543,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
@@ -552,7 +551,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
- mDisplayId,
+ displayId,
adapter),
new UserHandle(UserHandle.getUserId(appUid))));
});
@@ -571,6 +570,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startHistoryIntent(View view, boolean showHistory) {
ModesEmptyShadeFix.assertInLegacyMode();
+ final int displayId = mContext.getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -597,13 +597,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intent.getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mDisplayId, adapter),
+ getActivityOptions(displayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
@@ -620,6 +620,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startSettingsIntent(@NonNull View view, @NonNull SettingsIntent intentInfo) {
+ final int displayId = mContext.getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -642,13 +643,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intentInfo.getTargetIntent().getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mDisplayId, adapter),
+ getActivityOptions(displayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
index 7207d0aef3ee..4d531b512dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
@@ -20,6 +20,7 @@ import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.DisplaySpecific;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
import com.android.systemui.statusbar.layout.StatusBarBoundsProvider;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -85,7 +86,9 @@ public interface HomeStatusBarComponent {
default void init() {
// No one accesses these controllers, so we need to make sure we reference them here so they
// do get initialized.
- getBatteryMeterViewController().init();
+ if (!NewStatusBarIcons.isEnabled()) {
+ getBatteryMeterViewController().init();
+ }
getHeadsUpAppearanceController().init();
getPhoneStatusBarViewController().init();
if (!NotificationsLiveDataStoreRefactor.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
index 903844efa3f0..9665c33ac4c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.pipeline.battery.ui.binder
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.isVisible
@@ -27,6 +30,8 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
import com.android.systemui.statusbar.pipeline.battery.ui.composable.UnifiedBattery
import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_HEIGHT
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_WIDTH
import kotlinx.coroutines.flow.Flow
/** In cases where the battery needs to be bound to an existing android view */
@@ -47,7 +52,13 @@ object UnifiedBatteryViewBinder {
)
setContent {
val isDark by isAreaDark.collectAsStateWithLifecycle(IsAreaDark { true })
- UnifiedBattery(viewModelFactory = viewModelFactory, isDark = isDark)
+ UnifiedBattery(
+ modifier =
+ Modifier.height(STATUS_BAR_BATTERY_HEIGHT)
+ .width(STATUS_BAR_BATTERY_WIDTH),
+ viewModelFactory = viewModelFactory,
+ isDark = isDark,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
index 2ee86ee0a679..ac793a9c97e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
@@ -54,7 +54,7 @@ fun BatteryWithEstimate(
)
if (showEstimate) {
viewModel.batteryTimeRemainingEstimate?.let {
- Spacer(modifier.width(2.dp))
+ Spacer(modifier.width(4.dp))
Text(text = it, color = Color.White)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
index d0d099e74cb1..afd4bb1f36c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.battery.ui.viewmodel
import android.content.Context
import androidx.compose.runtime.getValue
+import androidx.compose.ui.unit.dp
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -223,6 +224,10 @@ constructor(interactor: BatteryInteractor, @Application context: Context) : Excl
}
companion object {
+ // Status bar battery height, based on a 21x12 base canvas
+ val STATUS_BAR_BATTERY_HEIGHT = 13.dp
+ val STATUS_BAR_BATTERY_WIDTH = 22.75.dp
+
fun Int.glyphRepresentation(): List<BatteryGlyph> = toString().map { it.toGlyph() }
private fun Char.toGlyph(): BatteryGlyph =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 9d72daf01831..c34fa464cc3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -23,6 +23,8 @@ import android.widget.LinearLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -34,9 +36,11 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.keyguard.AlphaOptimizedLinearLayout
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
@@ -51,6 +55,9 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ui.DarkIconManager
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.UnifiedBattery
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_HEIGHT
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_WIDTH
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
@@ -73,14 +80,13 @@ constructor(
) {
fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
val composeView = ComposeView(root.context)
- val displayId = root.context.displayId
val darkIconDispatcher =
darkIconDispatcherStore.forDisplay(root.context.displayId) ?: return composeView
composeView.apply {
setContent {
StatusBarRoot(
parent = root,
- statusBarViewModel = homeStatusBarViewModelFactory.create(displayId),
+ statusBarViewModelFactory = homeStatusBarViewModelFactory,
statusBarViewBinder = homeStatusBarViewBinder,
notificationIconsBinder = notificationIconsBinder,
darkIconManagerFactory = darkIconManagerFactory,
@@ -110,7 +116,7 @@ constructor(
@Composable
fun StatusBarRoot(
parent: ViewGroup,
- statusBarViewModel: HomeStatusBarViewModel,
+ statusBarViewModelFactory: HomeStatusBarViewModelFactory,
statusBarViewBinder: HomeStatusBarViewBinder,
notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
darkIconManagerFactory: DarkIconManager.Factory,
@@ -120,6 +126,10 @@ fun StatusBarRoot(
eventAnimationInteractor: SystemStatusEventAnimationInteractor,
onViewCreated: (ViewGroup) -> Unit,
) {
+ val displayId = parent.context.displayId
+ val statusBarViewModel =
+ rememberViewModel("HomeStatusBar") { statusBarViewModelFactory.create(displayId) }
+
Box(Modifier.fillMaxSize()) {
// TODO(b/364360986): remove this before rolling the flag forward
if (StatusBarRootModernization.SHOW_DISAMBIGUATION) {
@@ -159,10 +169,6 @@ fun StatusBarRoot(
LinearLayout.LayoutParams.WRAP_CONTENT,
)
- setViewCompositionStrategy(
- ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
- )
-
setContent {
PlatformTheme {
val chips by
@@ -241,6 +247,12 @@ fun StatusBarRoot(
endSideContent.addView(composeView, 0)
}
+ // If the flag is enabled, create and add a compose battery view to the end
+ // of the system_icons container
+ if (NewStatusBarIcons.isEnabled) {
+ addBatteryComposable(phoneStatusBarView, statusBarViewModel)
+ }
+
notificationIconsBinder.bindWhileAttached(
notificationIconContainer,
context.displayId,
@@ -263,6 +275,27 @@ fun StatusBarRoot(
}
}
+/** Create a new [UnifiedBattery] and add it to the end of the system_icons container */
+private fun addBatteryComposable(
+ phoneStatusBarView: PhoneStatusBarView,
+ statusBarViewModel: HomeStatusBarViewModel,
+) {
+ val batteryComposeView =
+ ComposeView(phoneStatusBarView.context).apply {
+ setContent {
+ UnifiedBattery(
+ modifier =
+ Modifier.height(STATUS_BAR_BATTERY_HEIGHT).width(STATUS_BAR_BATTERY_WIDTH),
+ viewModelFactory = statusBarViewModel.batteryViewModelFactory,
+ isDark = statusBarViewModel.areaDark,
+ )
+ }
+ }
+ phoneStatusBarView.findViewById<ViewGroup>(R.id.system_icons).apply {
+ addView(batteryComposeView, -1)
+ }
+}
+
/**
* This is our analog of the flexi "ribbon", which just shows some text so we know if the flag is on
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 1bc45a95044c..f396cbfc8000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.annotation.ColorInt
import android.graphics.Rect
import android.view.View
+import androidx.compose.runtime.getValue
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -28,6 +29,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.DarkIconDispatcher
@@ -55,8 +58,10 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
@@ -90,6 +95,9 @@ import kotlinx.coroutines.flow.stateIn
* so that it's all in one place and easily testable outside of the fragment.
*/
interface HomeStatusBarViewModel {
+ /** Factory to create the view model for the battery icon */
+ val batteryViewModelFactory: BatteryViewModel.Factory
+
/**
* True if the device is currently transitioning from lockscreen to occluded and false
* otherwise.
@@ -171,6 +179,9 @@ interface HomeStatusBarViewModel {
*/
val areaTint: Flow<StatusBarTintColor>
+ /** [IsAreaDark] applicable for this status bar's display and content area */
+ val areaDark: IsAreaDark
+
/** Interface for the assisted factory, to allow for providing a fake in tests */
interface HomeStatusBarViewModelFactory {
fun create(displayId: Int): HomeStatusBarViewModel
@@ -181,6 +192,7 @@ class HomeStatusBarViewModelImpl
@AssistedInject
constructor(
@Assisted thisDisplayId: Int,
+ override val batteryViewModelFactory: BatteryViewModel.Factory,
tableLoggerFactory: TableLogBufferFactory,
homeStatusBarInteractor: HomeStatusBarInteractor,
homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
@@ -201,7 +213,9 @@ constructor(
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Background bgScope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
-) : HomeStatusBarViewModel {
+) : HomeStatusBarViewModel, ExclusiveActivatable() {
+
+ private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
@@ -294,6 +308,13 @@ constructor(
.distinctUntilChanged()
.flowOn(bgDispatcher)
+ override val areaDark: IsAreaDark by
+ hydrator.hydratedStateOf(
+ traceName = "areaDark",
+ initialValue = IsAreaDark { true },
+ source = darkIconInteractor.isAreaDark(thisDisplayId),
+ )
+
/**
* True if the current SysUI state can show the home status bar (aka this status bar), and false
* if we shouldn't be showing any part of the home status bar.
@@ -473,6 +494,10 @@ constructor(
@View.Visibility
private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
/** Inject this to create the display-dependent view model */
@AssistedFactory
interface HomeStatusBarViewModelFactoryImpl :
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index cd401d5deb6e..e1640cd4ce7a 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -214,7 +214,12 @@ constructor(
private suspend fun waitForScreenTurnedOn() {
traceAsync(TAG, "waitForScreenTurnedOn()") {
- powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ // dropping first as it's stateFlow and will always emit latest value but we're
+ // only interested in new states
+ powerInteractor.screenPowerState
+ .drop(1)
+ .filter { it == ScreenPowerState.SCREEN_ON }
+ .first()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
index 6ac0bb168f18..91f142646c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
@@ -50,6 +50,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
@@ -160,7 +161,12 @@ constructor(
private suspend fun waitForScreenTurnedOn() {
traceAsync(TAG, "waitForScreenTurnedOn()") {
- powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ // dropping first as it's stateFlow and will always emit latest value but we're
+ // only interested in new states
+ powerInteractor.screenPowerState
+ .drop(1)
+ .filter { it == ScreenPowerState.SCREEN_ON }
+ .first()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 68bffeefb0f0..4d5477052388 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -37,8 +37,6 @@ import android.media.IAudioService;
import android.media.IVolumeController;
import android.media.MediaRouter2Manager;
import android.media.VolumePolicy;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -61,6 +59,7 @@ import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.volume.MediaSessions;
+import com.android.settingslib.volume.MediaSessions.SessionId;
import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -1402,12 +1401,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
- private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+ private final HashMap<SessionId, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
@Override
- public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+ public void onRemoteUpdate(
+ SessionId token, String name, MediaSessions.VolumeInfo volumeInfo) {
addStream(token, "onRemoteUpdate");
int stream = 0;
@@ -1415,14 +1415,15 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
stream = mRemoteStreams.get(token);
}
Slog.d(TAG,
- "onRemoteUpdate: stream: " + stream + " volume: " + pi.getCurrentVolume());
+ "onRemoteUpdate: stream: "
+ + stream + " volume: " + volumeInfo.getCurrentVolume());
boolean changed = mState.states.indexOfKey(stream) < 0;
final StreamState ss = streamStateW(stream);
ss.dynamic = true;
ss.levelMin = 0;
- ss.levelMax = pi.getMaxVolume();
- if (ss.level != pi.getCurrentVolume()) {
- ss.level = pi.getCurrentVolume();
+ ss.levelMax = volumeInfo.getMaxVolume();
+ if (ss.level != volumeInfo.getCurrentVolume()) {
+ ss.level = volumeInfo.getCurrentVolume();
changed = true;
}
if (!Objects.equals(ss.remoteLabel, name)) {
@@ -1437,11 +1438,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
@Override
- public void onRemoteVolumeChanged(Token token, int flags) {
- addStream(token, "onRemoteVolumeChanged");
+ public void onRemoteVolumeChanged(SessionId sessionId, int flags) {
+ addStream(sessionId, "onRemoteVolumeChanged");
int stream = 0;
synchronized (mRemoteStreams) {
- stream = mRemoteStreams.get(token);
+ stream = mRemoteStreams.get(sessionId);
}
final boolean showUI = shouldShowUI(flags);
Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
@@ -1459,7 +1460,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
@Override
- public void onRemoteRemoved(Token token) {
+ public void onRemoteRemoved(SessionId token) {
int stream;
synchronized (mRemoteStreams) {
if (!mRemoteStreams.containsKey(token)) {
@@ -1480,7 +1481,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
public void setStreamVolume(int stream, int level) {
- final Token token = findToken(stream);
+ final SessionId token = findToken(stream);
if (token == null) {
Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
return;
@@ -1488,9 +1489,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mMediaSessions.setVolume(token, level);
}
- private Token findToken(int stream) {
+ private SessionId findToken(int stream) {
synchronized (mRemoteStreams) {
- for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+ for (Map.Entry<SessionId, Integer> entry : mRemoteStreams.entrySet()) {
if (entry.getValue().equals(stream)) {
return entry.getKey();
}
@@ -1499,7 +1500,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
return null;
}
- private void addStream(Token token, String triggeringMethod) {
+ private void addStream(SessionId token, String triggeringMethod) {
synchronized (mRemoteStreams) {
if (!mRemoteStreams.containsKey(token)) {
mRemoteStreams.put(token, mNextStream);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 83b7c1818341..86defff4a120 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -68,7 +68,7 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.volume_dialog)
- requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached {
+ requireViewById<View>(R.id.volume_dialog).repeatWhenAttached {
coroutineScopeTraced("[Volume]dialog") {
val component = componentFactory.create(this)
with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 20a74b027db5..afe3d7bf217a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.volume.dialog.domain.interactor
import android.annotation.SuppressLint
+import android.provider.Settings
import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
@@ -28,8 +30,9 @@ import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityMod
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel.Visible
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
-import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -43,8 +46,6 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-private val MAX_DIALOG_SHOW_TIME: Duration = 3.seconds
-
/**
* Handles Volume Dialog visibility state. It might change from several sources:
* - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
@@ -60,8 +61,11 @@ constructor(
private val tracer: VolumeTracer,
private val repository: VolumeDialogVisibilityRepository,
private val controller: VolumeDialogController,
+ private val secureSettingsRepository: SecureSettingsRepository,
) {
+ private val defaultTimeout = 3.seconds
+
@SuppressLint("SharedFlowCreation")
private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val dialogVisibility: Flow<VolumeDialogVisibilityModel> =
@@ -73,7 +77,14 @@ constructor(
init {
merge(
mutableDismissDialogEvents.mapLatest {
- delay(MAX_DIALOG_SHOW_TIME)
+ delay(
+ secureSettingsRepository
+ .getInt(
+ Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
+ defaultTimeout.toInt(DurationUnit.MILLISECONDS),
+ )
+ .milliseconds
+ )
VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
},
callbacksInteractor.event,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 3d0c7d64b2a4..92ec4f554548 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -246,16 +246,12 @@ constructor(
uiModel.drawerState.currentMode != uiModel.drawerState.previousMode
) {
val count = uiModel.availableButtons.size
- val selectedButton =
- getChildAt(count - uiModel.currentButtonIndex)
- .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val selectedButton = getChildAt(count - uiModel.currentButtonIndex) as ImageButton
val previousIndex =
uiModel.availableButtons.indexOfFirst {
it.ringerMode == uiModel.drawerState.previousMode
}
- val unselectedButton =
- getChildAt(count - previousIndex)
- .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val unselectedButton = getChildAt(count - previousIndex) as ImageButton
// We only need to execute on roundness animation end and volume dialog background
// progress update once because these changes should be applied once on volume dialog
// background and ringer drawer views.
@@ -306,7 +302,7 @@ constructor(
) {
val count = uiModel.availableButtons.size
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
- val view = getChildAt(count - index)
+ val view = getChildAt(count - index) as ImageButton
val isOpen = uiModel.drawerState is RingerDrawerState.Open
if (index == uiModel.currentButtonIndex) {
view.bindDrawerButton(
@@ -323,37 +319,37 @@ constructor(
onAnimationEnd?.run()
}
- private fun View.bindDrawerButton(
+ private fun ImageButton.bindDrawerButton(
buttonViewModel: RingerButtonViewModel,
viewModel: VolumeDialogRingerDrawerViewModel,
isOpen: Boolean,
isSelected: Boolean = false,
isAnimated: Boolean = false,
) {
+ // id = buttonViewModel.viewId
+ setSelected(isSelected)
val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
- with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
- setImageResource(buttonViewModel.imageResId)
- contentDescription =
- if (isSelected && !isOpen) {
- context.getString(
- R.string.volume_ringer_drawer_closed_content_description,
- ringerContentDesc,
- )
- } else {
- ringerContentDesc
- }
- if (isSelected && !isAnimated) {
- setBackgroundResource(R.drawable.volume_drawer_selection_bg)
- setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
- background = background.mutate()
- } else if (!isAnimated) {
- setBackgroundResource(R.drawable.volume_ringer_item_bg)
- setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
- background = background.mutate()
- }
- setOnClickListener {
- viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
+ setImageResource(buttonViewModel.imageResId)
+ contentDescription =
+ if (isSelected && !isOpen) {
+ context.getString(
+ R.string.volume_ringer_drawer_closed_content_description,
+ ringerContentDesc,
+ )
+ } else {
+ ringerContentDesc
}
+ if (isSelected && !isAnimated) {
+ setBackgroundResource(R.drawable.volume_drawer_selection_bg)
+ setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
+ background = background.mutate()
+ } else if (!isAnimated) {
+ setBackgroundResource(R.drawable.volume_ringer_item_bg)
+ setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
+ background = background.mutate()
+ }
+ setOnClickListener {
+ viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index f2d7d956291c..7cc4bcc4e11c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -74,7 +74,7 @@ constructor(
val insets: MutableStateFlow<WindowInsets> =
MutableStateFlow(WindowInsets.Builder().build())
// Root view of the Volume Dialog.
- val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+ val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog)
animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
diff --git a/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt
new file mode 100644
index 000000000000..95b3b68fa1ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.window.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that can be installed in sysui variants where we support cross window blur.
+ */
+@Module
+interface WindowRootViewBlurModule {
+ @Binds
+ @SysUISingleton
+ fun bindWindowRootViewBlurRepository(
+ windowRootViewBlurRepositoryImpl: WindowRootViewBlurRepositoryImpl
+ ): WindowRootViewBlurRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt
new file mode 100644
index 000000000000..ae917e072ff3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.window.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.window.data.repository.NoopWindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that can be installed in sysui variants where we don't support cross window blur.
+ */
+@Module
+interface WindowRootViewBlurNotSupportedModule {
+ @Binds
+ @SysUISingleton
+ fun bindWindowRootViewBlurRepository(
+ windowRootViewBlurRepositoryImpl: NoopWindowRootViewBlurRepository
+ ): WindowRootViewBlurRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
new file mode 100644
index 000000000000..80aa11a71569
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 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.window.data.repository
+
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class NoopWindowRootViewBlurRepository @Inject constructor() : WindowRootViewBlurRepository {
+ override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
+ override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isBlurSupported: StateFlow<Boolean> = MutableStateFlow(false)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
index 22a74c86e0f1..41ceda033bdf 100644
--- a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
@@ -16,14 +16,77 @@
package com.android.systemui.window.data.repository
+import android.app.ActivityManager
+import android.os.SystemProperties
+import android.view.CrossWindowBlurListeners
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository.Companion.isDisableBlurSysPropSet
+import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
/** Repository that maintains state for the window blur effect. */
+interface WindowRootViewBlurRepository {
+ val blurRadius: MutableStateFlow<Int>
+ val isBlurOpaque: MutableStateFlow<Boolean>
+
+ /** Is blur supported based on settings toggle and battery power saver mode. */
+ val isBlurSupported: StateFlow<Boolean>
+
+ companion object {
+ /**
+ * Whether the `persist.sysui.disableBlur` is set, this is used to disable blur for tests.
+ */
+ @JvmStatic
+ fun isDisableBlurSysPropSet() = SystemProperties.getBoolean(DISABLE_BLUR_PROPERTY, false)
+
+ // property that can be used to disable the cross window blur for tests
+ private const val DISABLE_BLUR_PROPERTY = "persist.sysui.disableBlur"
+ }
+}
+
@SysUISingleton
-class WindowRootViewBlurRepository @Inject constructor() {
- val blurRadius = MutableStateFlow(0)
+class WindowRootViewBlurRepositoryImpl
+@Inject
+constructor(
+ crossWindowBlurListeners: CrossWindowBlurListeners,
+ @Main private val executor: Executor,
+ @Application private val scope: CoroutineScope,
+) : WindowRootViewBlurRepository {
+ override val blurRadius = MutableStateFlow(0)
+
+ override val isBlurOpaque = MutableStateFlow(false)
+
+ override val isBlurSupported: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val sendUpdate = { value: Boolean ->
+ trySendWithFailureLogging(
+ isBlurAllowed() && value,
+ TAG,
+ "unable to send blur enabled/disable state change",
+ )
+ }
+ crossWindowBlurListeners.addListener(executor, sendUpdate)
+ sendUpdate(crossWindowBlurListeners.isCrossWindowBlurEnabled)
+
+ awaitClose { crossWindowBlurListeners.removeListener(sendUpdate) }
+ } // stateIn because this is backed by a binder call.
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private fun isBlurAllowed(): Boolean {
+ return ActivityManager.isHighEndGfx() && !isDisableBlurSysPropSet()
+ }
- val isBlurOpaque = MutableStateFlow(false)
+ companion object {
+ const val TAG = "WindowRootViewBlurRepository"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
index 9e369347dea5..7a88a2ef966b 100644
--- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
@@ -75,6 +75,12 @@ constructor(
_onBlurAppliedEvent.emit(appliedBlurRadius)
}
+ /**
+ * Whether blur is enabled or not based on settings toggle, critical thermal state, battery save
+ * state and multimedia tunneling state.
+ */
+ val isBlurCurrentlySupported: StateFlow<Boolean> = repository.isBlurSupported
+
/** Radius of blur to be applied on the window root view. */
val blurRadius: StateFlow<Int> = repository.blurRadius.asStateFlow()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 732561e0979b..944604f94ce4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -640,11 +640,11 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
- @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
with(kosmos) {
testScope.runTest {
+ `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
// On lockscreen.
goToScene(CommunalScenes.Blank)
whenever(
@@ -721,11 +721,11 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
- @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
testScope.runTest {
+ `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
// On lockscreen.
goToScene(CommunalScenes.Blank)
whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index edb0f352a64a..f3af794f776b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -59,6 +59,7 @@ import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -202,6 +203,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
Lazy { kosmos.shadeDisplaysRepository },
variableDateViewControllerFactory,
batteryMeterViewController,
+ kosmos.batteryViewModelFactory,
dumpManager,
mShadeCarrierGroupControllerBuilder,
combinedShadeHeadersConstraintManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a5234883ed77..14a1233045bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -290,7 +290,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mKeyguardInteractor,
mKosmos.getTestDispatcher(),
mLinearLargeScreenShadeInterpolator,
- new BlurConfig(0.0f, 0.0f));
+ new BlurConfig(0.0f, 0.0f),
+ mKosmos::getWindowRootViewBlurInteractor);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1204,7 +1205,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mKeyguardInteractor,
mKosmos.getTestDispatcher(),
mLinearLargeScreenShadeInterpolator,
- new BlurConfig(0.0f, 0.0f));
+ new BlurConfig(0.0f, 0.0f),
+ mKosmos::getWindowRootViewBlurInteractor);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 2f30b745a4a3..3190d3ae8f16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -91,11 +91,11 @@ import com.android.systemui.statusbar.notification.collection.provider.LaunchFul
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -122,7 +122,6 @@ import java.util.Optional;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
- private static final int DISPLAY_ID = 0;
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
@@ -233,7 +232,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter(
getContext(),
- DISPLAY_ID,
mHandler,
mUiBgExecutor,
mVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index fcdda9f13099..9da8e80283b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.service.dream.dreamManager
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -44,5 +45,6 @@ var Kosmos.fromDozingTransitionInteractor by
deviceEntryInteractor = deviceEntryInteractor,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
dreamManager = dreamManager,
+ communalSettingsInteractor = communalSettingsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index b255b51281af..044332981bf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -57,12 +57,10 @@ fun Kosmos.useUnconfinedTestDispatcher() = apply { testDispatcher = UnconfinedTe
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
var Kosmos.backgroundScope by Fixture { testScope.backgroundScope }
-var Kosmos.applicationCoroutineScope by Fixture { backgroundScope }
+var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
-var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
- backgroundScope.coroutineContext
-}
-var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture { testDispatcher }
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testDispatcher }
/**
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 446e10671afb..60b371aa8afb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -90,6 +90,7 @@ import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisionin
import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
/**
* Helper for using [Kosmos] from Java.
@@ -192,4 +193,5 @@ class KosmosJavaAdapter() {
val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor }
val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
+ val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index 5fc31f8b9e10..f2871149de11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.pipeline.data.repository
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -79,9 +80,9 @@ class FakeTileSpecRepository(
with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles }
}
- override val tilesReadFromSetting: Channel<Pair<Set<TileSpec>, Int>> = Channel(capacity = 10)
+ override val tilesUpgradePath: Channel<Pair<TilesUpgradePath, Int>> = Channel(capacity = 10)
- suspend fun sendTilesReadFromSetting(tiles: Set<TileSpec>, userId: Int) {
- tilesReadFromSetting.send(tiles to userId)
+ suspend fun sendTilesFromUpgradePath(upgradePath: TilesUpgradePath, userId: Int) {
+ tilesUpgradePath.send(upgradePath to userId)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 5ff44e5d33c5..c5de02a7281b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -26,7 +26,7 @@ val Kosmos.minimumTilesRepository: MinimumTilesRepository by
Kosmos.Fixture { fakeMinimumTilesRepository }
var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
-val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+var Kosmos.defaultTilesRepository: DefaultTilesRepository by
Kosmos.Fixture { fakeDefaultTilesRepository }
val Kosmos.fakeTileSpecRepository by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 0d6ac4481742..d787e2c190c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -52,7 +52,6 @@ val Kosmos.statusBarNotificationActivityStarter by
Kosmos.Fixture {
StatusBarNotificationActivityStarter(
applicationContext,
- applicationContext.displayId,
fakeExecutorHandler,
fakeExecutor,
notificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
index c6cf0063986a..7dd0103e9f5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
@@ -22,3 +22,10 @@ import com.android.systemui.statusbar.pipeline.battery.domain.interactor.battery
val Kosmos.batteryViewModel by
Kosmos.Fixture { BatteryViewModel(batteryInteractor, testableContext) }
+
+val Kosmos.batteryViewModelFactory by
+ Kosmos.Fixture {
+ object : BatteryViewModel.Factory {
+ override fun create(): BatteryViewModel = batteryViewModel
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index bc29dba3442c..fbada934c9d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.activeNotif
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarInteractor
@@ -42,6 +43,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
testableContext.displayId,
+ batteryViewModelFactory,
tableLogBufferFactory,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 0d2aa4c79753..888b7e625524 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
import com.android.systemui.volume.dialog.utils.volumeTracer
@@ -30,5 +31,6 @@ val Kosmos.volumeDialogVisibilityInteractor by
volumeTracer,
volumeDialogVisibilityRepository,
volumeDialogController,
+ secureSettingsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
index 7281e03a5ea4..96992233375d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
@@ -17,5 +17,16 @@
package com.android.systemui.window.data.repository
import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.flow.MutableStateFlow
-val Kosmos.windowRootViewBlurRepository by Kosmos.Fixture { WindowRootViewBlurRepository() }
+val Kosmos.fakeWindowRootViewBlurRepository: FakeWindowRootViewBlurRepository by
+ Kosmos.Fixture { FakeWindowRootViewBlurRepository() }
+
+val Kosmos.windowRootViewBlurRepository: WindowRootViewBlurRepository by
+ Kosmos.Fixture { fakeWindowRootViewBlurRepository }
+
+class FakeWindowRootViewBlurRepository : WindowRootViewBlurRepository {
+ override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
+ override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isBlurSupported: MutableStateFlow<Boolean> = MutableStateFlow(false)
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index db8441d2424b..5283df5ca7e1 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -17,11 +17,16 @@
package com.android.server.accessibility.autoclick;
import static android.view.MotionEvent.BUTTON_PRIMARY;
+import static android.view.MotionEvent.BUTTON_SECONDARY;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
@@ -84,6 +89,23 @@ public class AutoclickController extends BaseEventStreamTransformation {
@VisibleForTesting AutoclickTypePanel mAutoclickTypePanel;
private WindowManager mWindowManager;
+ // Default click type is left-click.
+ private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+
+ @VisibleForTesting
+ final ClickPanelControllerInterface clickPanelController =
+ new ClickPanelControllerInterface() {
+ @Override
+ public void handleAutoclickTypeChange(@AutoclickType int clickType) {
+ mActiveClickType = clickType;
+ }
+
+ @Override
+ public void toggleAutoclickPause() {
+ // TODO(b/388872274): allows users to pause the autoclick.
+ }
+ };
+
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
mTrace = trace;
mContext = context;
@@ -124,7 +146,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorView = new AutoclickIndicatorView(mContext);
mWindowManager = mContext.getSystemService(WindowManager.class);
- mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager);
+ mAutoclickTypePanel =
+ new AutoclickTypePanel(mContext, mWindowManager, clickPanelController);
mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
@@ -644,6 +667,15 @@ public class AutoclickController extends BaseEventStreamTransformation {
final long now = SystemClock.uptimeMillis();
+ // TODO(b/395094903): always triggers left-click when the cursor hovers over the
+ // autoclick type panel, to always allow users to change a different click type.
+ // Otherwise, if one chooses the right-click, this user won't be able to rely on
+ // autoclick to select other click types.
+ final int actionButton =
+ mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
+ ? BUTTON_SECONDARY
+ : BUTTON_PRIMARY;
+
MotionEvent downEvent =
MotionEvent.obtain(
/* downTime= */ now,
@@ -653,7 +685,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
mTempPointerProperties,
mTempPointerCoords,
mMetaState,
- BUTTON_PRIMARY,
+ actionButton,
/* xPrecision= */ 1.0f,
/* yPrecision= */ 1.0f,
mLastMotionEvent.getDeviceId(),
@@ -663,11 +695,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
MotionEvent pressEvent = MotionEvent.obtain(downEvent);
pressEvent.setAction(MotionEvent.ACTION_BUTTON_PRESS);
- pressEvent.setActionButton(BUTTON_PRIMARY);
+ pressEvent.setActionButton(actionButton);
MotionEvent releaseEvent = MotionEvent.obtain(downEvent);
releaseEvent.setAction(MotionEvent.ACTION_BUTTON_RELEASE);
- releaseEvent.setActionButton(BUTTON_PRIMARY);
+ releaseEvent.setActionButton(actionButton);
releaseEvent.setButtonState(0);
MotionEvent upEvent = MotionEvent.obtain(downEvent);
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 2ef11f4b78e1..cf928e2f3fa4 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -18,6 +18,7 @@ package com.android.server.accessibility.autoclick;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import android.annotation.IntDef;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
@@ -39,12 +40,40 @@ public class AutoclickTypePanel {
private final String TAG = AutoclickTypePanel.class.getSimpleName();
+ public static final int AUTOCLICK_TYPE_LEFT_CLICK = 0;
+ public static final int AUTOCLICK_TYPE_RIGHT_CLICK = 1;
+ public static final int AUTOCLICK_TYPE_DOUBLE_CLICK = 2;
+ public static final int AUTOCLICK_TYPE_DRAG = 3;
+ public static final int AUTOCLICK_TYPE_SCROLL = 4;
+
+ // Types of click the AutoclickTypePanel supports.
+ @IntDef({
+ AUTOCLICK_TYPE_LEFT_CLICK,
+ AUTOCLICK_TYPE_RIGHT_CLICK,
+ AUTOCLICK_TYPE_DOUBLE_CLICK,
+ AUTOCLICK_TYPE_DRAG,
+ AUTOCLICK_TYPE_SCROLL,
+ })
+ public @interface AutoclickType {}
+
+ // An interface exposed to {@link AutoclickController) to handle different actions on the panel,
+ // including changing autoclick type, pausing/resuming autoclick.
+ public interface ClickPanelControllerInterface {
+ // Allows users to change a different autoclick type.
+ void handleAutoclickTypeChange(@AutoclickType int clickType);
+
+ // Allows users to pause/resume the autoclick.
+ void toggleAutoclickPause();
+ }
+
private final Context mContext;
private final View mContentView;
private final WindowManager mWindowManager;
+ private final ClickPanelControllerInterface mClickPanelController;
+
// Whether the panel is expanded or not.
private boolean mExpanded = false;
@@ -56,9 +85,13 @@ public class AutoclickTypePanel {
private LinearLayout mSelectedButton;
- public AutoclickTypePanel(Context context, WindowManager windowManager) {
+ public AutoclickTypePanel(
+ Context context,
+ WindowManager windowManager,
+ ClickPanelControllerInterface clickPanelController) {
mContext = context;
mWindowManager = windowManager;
+ mClickPanelController = clickPanelController;
mContentView =
LayoutInflater.from(context)
@@ -76,26 +109,35 @@ public class AutoclickTypePanel {
}
private void initializeButtonState() {
- mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(mLeftClickButton));
- mRightClickButton.setOnClickListener(v -> togglePanelExpansion(mRightClickButton));
- mDoubleClickButton.setOnClickListener(v -> togglePanelExpansion(mDoubleClickButton));
- mScrollButton.setOnClickListener(v -> togglePanelExpansion(mScrollButton));
- mDragButton.setOnClickListener(v -> togglePanelExpansion(mDragButton));
+ mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_LEFT_CLICK));
+ mRightClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_RIGHT_CLICK));
+ mDoubleClickButton.setOnClickListener(
+ v -> togglePanelExpansion(AUTOCLICK_TYPE_DOUBLE_CLICK));
+ mScrollButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_SCROLL));
+ mDragButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_DRAG));
+
+ // TODO(b/388872274): registers listener for pause button and allows users to pause/resume
+ // the autoclick.
+ // TODO(b/388847771): registers listener for position button and allows users to move the
+ // panel to a different position.
// Initializes panel as collapsed state and only displays the left click button.
hideAllClickTypeButtons();
mLeftClickButton.setVisibility(View.VISIBLE);
- setSelectedButton(/* selectedButton= */ mLeftClickButton);
+ setSelectedClickType(AUTOCLICK_TYPE_LEFT_CLICK);
}
/** Sets the selected button and updates the newly and previously selected button styling. */
- private void setSelectedButton(@NonNull LinearLayout selectedButton) {
+ private void setSelectedClickType(@AutoclickType int clickType) {
+ final LinearLayout selectedButton = getButtonFromClickType(clickType);
+
// Updates the previously selected button styling.
if (mSelectedButton != null) {
toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false);
}
mSelectedButton = selectedButton;
+ mClickPanelController.handleAutoclickTypeChange(clickType);
// Updates the newly selected button styling.
toggleSelectedButtonStyle(selectedButton, /* isSelected= */ true);
@@ -130,7 +172,9 @@ public class AutoclickTypePanel {
}
/** Toggles the panel expanded or collapsed state. */
- private void togglePanelExpansion(LinearLayout button) {
+ private void togglePanelExpansion(@AutoclickType int clickType) {
+ final LinearLayout button = getButtonFromClickType(clickType);
+
if (mExpanded) {
// If the panel is already in expanded state, we should collapse it by hiding all
// buttons except the one user selected.
@@ -138,7 +182,7 @@ public class AutoclickTypePanel {
button.setVisibility(View.VISIBLE);
// Sets the newly selected button.
- setSelectedButton(/* selectedButton= */ button);
+ setSelectedClickType(clickType);
} else {
// If the panel is already collapsed, we just need to expand it.
showAllClickTypeButtons();
@@ -166,6 +210,17 @@ public class AutoclickTypePanel {
mScrollButton.setVisibility(View.VISIBLE);
}
+ private LinearLayout getButtonFromClickType(@AutoclickType int clickType) {
+ return switch (clickType) {
+ case AUTOCLICK_TYPE_LEFT_CLICK -> mLeftClickButton;
+ case AUTOCLICK_TYPE_RIGHT_CLICK -> mRightClickButton;
+ case AUTOCLICK_TYPE_DOUBLE_CLICK -> mDoubleClickButton;
+ case AUTOCLICK_TYPE_DRAG -> mDragButton;
+ case AUTOCLICK_TYPE_SCROLL -> mScrollButton;
+ default -> throw new IllegalArgumentException("Unknown clickType " + clickType);
+ };
+ }
+
@VisibleForTesting
boolean getExpansionStateForTesting() {
return mExpanded;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 414db37508e5..05301fdd8385 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -588,10 +588,10 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
@EnforcePermission(DELIVER_COMPANION_MESSAGES)
public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
+ ParcelFileDescriptor fd, int flags) {
attachSystemDataTransport_enforcePermission();
- mTransportManager.attachSystemDataTransport(associationId, fd);
+ mTransportManager.attachSystemDataTransport(associationId, fd, flags);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
index df3071e08a03..42af0597b35c 100644
--- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -16,7 +16,9 @@
package com.android.server.companion.securechannel;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS;
import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE;
import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
@@ -34,15 +36,21 @@ import java.util.function.BiConsumer;
/**
* Helper class to perform attestation verification synchronously.
+ *
+ * @hide
*/
public class AttestationVerifier {
private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
+ private static final int EXTENDED_PATCH_LEVEL_DIFF_MONTHS = 24; // 2 years
+
private final Context mContext;
+ private final int mFlags;
- AttestationVerifier(Context context) {
+ AttestationVerifier(Context context, int flags) {
this.mContext = context;
+ this.mFlags = flags;
}
/**
@@ -59,10 +67,13 @@ public class AttestationVerifier {
@NonNull byte[] remoteAttestation,
@NonNull byte[] attestationChallenge
) throws SecureChannelException {
- Bundle requirements = new Bundle();
+ final Bundle requirements = new Bundle();
requirements.putByteArray(PARAM_CHALLENGE, attestationChallenge);
requirements.putBoolean(PARAM_OWNED_BY_SYSTEM, true); // Custom parameter for CDM
+ // Apply flags to verifier requirements
+ updateRequirements(requirements);
+
// Synchronously execute attestation verification.
AtomicInteger verificationResult = new AtomicInteger(0);
CountDownLatch verificationFinished = new CountDownLatch(1);
@@ -96,4 +107,15 @@ public class AttestationVerifier {
return verificationResult.get();
}
+
+ private void updateRequirements(Bundle requirements) {
+ if (mFlags == 0) {
+ return;
+ }
+
+ if ((mFlags & TRANSPORT_FLAG_EXTEND_PATCH_DIFF) > 0) {
+ requirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS,
+ EXTENDED_PATCH_LEVEL_DIFF_MONTHS);
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 2d3782fb3181..6c7c9b3e073d 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -59,6 +59,7 @@ public class SecureChannel {
private final Callback mCallback;
private final byte[] mPreSharedKey;
private final AttestationVerifier mVerifier;
+ private final int mFlags;
private volatile boolean mStopped;
private volatile boolean mInProgress;
@@ -89,7 +90,7 @@ public class SecureChannel {
@NonNull Callback callback,
@NonNull byte[] preSharedKey
) {
- this(in, out, callback, preSharedKey, null);
+ this(in, out, callback, preSharedKey, null, 0);
}
/**
@@ -100,14 +101,16 @@ public class SecureChannel {
* @param out output stream from which data is sent out
* @param callback subscription to received messages from the channel
* @param context context for fetching the Attestation Verifier Framework system service
+ * @param flags flags for custom security settings on the channel
*/
public SecureChannel(
@NonNull final InputStream in,
@NonNull final OutputStream out,
@NonNull Callback callback,
- @NonNull Context context
+ @NonNull Context context,
+ int flags
) {
- this(in, out, callback, null, new AttestationVerifier(context));
+ this(in, out, callback, null, new AttestationVerifier(context, flags), flags);
}
public SecureChannel(
@@ -115,13 +118,15 @@ public class SecureChannel {
final OutputStream out,
Callback callback,
byte[] preSharedKey,
- AttestationVerifier verifier
+ AttestationVerifier verifier,
+ int flags
) {
this.mInput = in;
this.mOutput = out;
this.mCallback = callback;
this.mPreSharedKey = preSharedKey;
this.mVerifier = verifier;
+ this.mFlags = flags;
}
/**
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 36083607bfcd..92d9fb02de79 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,7 +16,11 @@
package com.android.server.companion.transport;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
+
+import static com.android.server.companion.transport.TransportUtils.enforceAssociationCanUseTransportFlags;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -152,10 +156,14 @@ public class CompanionTransportManager {
/**
* Attach transport.
*/
- public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
+ public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd,
+ int flags) {
Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
- mAssociationStore.getAssociationWithCallerChecks(associationId);
+ AssociationInfo association =
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
+ enforceAssociationCanUseTransportFlags(association, flags);
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
@@ -163,7 +171,7 @@ public class CompanionTransportManager {
}
// TODO: Implement new API to pass a PSK
- initializeTransport(associationId, fd, null);
+ initializeTransport(association, fd, null, flags);
notifyOnTransportsChanged();
}
@@ -217,10 +225,12 @@ public class CompanionTransportManager {
}
}
- private void initializeTransport(int associationId,
+ private void initializeTransport(AssociationInfo association,
ParcelFileDescriptor fd,
- byte[] preSharedKey) {
+ byte[] preSharedKey,
+ int flags) {
Slog.i(TAG, "Initializing transport");
+ int associationId = association.getId();
Transport transport;
if (!isSecureTransportEnabled()) {
// If secure transport is explicitly disabled for testing, use raw transport
@@ -230,15 +240,21 @@ public class CompanionTransportManager {
// If device is debug build, use hardcoded test key for authentication
Slog.d(TAG, "Creating an unauthenticated secure channel");
final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8);
- transport = new SecureTransport(associationId, fd, mContext, testKey, null);
+ transport = new SecureTransport(associationId, fd, mContext, testKey, null, 0);
} else if (preSharedKey != null) {
// If either device is not Android, then use app-specific pre-shared key
Slog.d(TAG, "Creating a PSK-authenticated secure channel");
- transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null);
+ transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null, 0);
+ } else if (DEVICE_PROFILE_WEARABLE_SENSING.equals(association.getDeviceProfile())) {
+ // If device is glasses with WEARABLE_SENSING profile, extend the allowed patch
+ // difference to 2 years instead of 1.
+ Slog.d(TAG, "Creating a secure channel with extended patch difference allowance");
+ transport = new SecureTransport(associationId, fd, mContext,
+ TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
} else {
// If none of the above applies, then use secure channel with attestation verification
Slog.d(TAG, "Creating a secure channel");
- transport = new SecureTransport(associationId, fd, mContext);
+ transport = new SecureTransport(associationId, fd, mContext, flags);
}
addMessageListenersToTransport(transport);
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 1e95e65848a5..77dc80998e2e 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -36,15 +36,22 @@ class SecureTransport extends Transport implements SecureChannel.Callback {
private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
- SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+ SecureTransport(int associationId, ParcelFileDescriptor fd, Context context, int flags) {
super(associationId, fd, context);
- mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context);
+ mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context, flags);
}
SecureTransport(int associationId, ParcelFileDescriptor fd, Context context,
- byte[] preSharedKey, AttestationVerifier verifier) {
+ byte[] preSharedKey, AttestationVerifier verifier, int flags) {
super(associationId, fd, context);
- mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, preSharedKey, verifier);
+ mSecureChannel = new SecureChannel(
+ mRemoteIn,
+ mRemoteOut,
+ this,
+ preSharedKey,
+ verifier,
+ flags
+ );
}
@Override
diff --git a/services/companion/java/com/android/server/companion/transport/TransportUtils.java b/services/companion/java/com/android/server/companion/transport/TransportUtils.java
new file mode 100644
index 000000000000..7a15c11afd19
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/TransportUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.companion.transport;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
+
+import static java.util.Collections.unmodifiableMap;
+
+import android.companion.AssociationInfo;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Utility class for transport manager.
+ * @hide
+ */
+public final class TransportUtils {
+
+ /**
+ * Device profile -> Union of allowlisted transport flags
+ */
+ private static final Map<String, Integer> DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST;
+ static {
+ final Map<String, Integer> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_WEARABLE_SENSING,
+ TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
+ DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST = unmodifiableMap(map);
+ }
+
+ /**
+ * Enforce that the association that is trying to attach a transport with provided flags has
+ * one of the allowlisted device profiles that may apply the flagged features.
+ *
+ * @param association Association for which transport is being attached
+ * @param flags Flags for features being applied to the transport
+ */
+ public static void enforceAssociationCanUseTransportFlags(
+ AssociationInfo association, int flags) {
+ if (flags == 0) {
+ return;
+ }
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (!DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST.containsKey(deviceProfile)) {
+ throw new IllegalArgumentException("Association (id=" + association.getId()
+ + ") with device profile " + deviceProfile + " does not support the "
+ + "usage of transport flags.");
+ }
+
+ int allowedFlags = DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST.get(deviceProfile);
+
+ // Ensure that every non-zero bits in flags are also present in allowed flags
+ if ((allowedFlags & flags) != flags) {
+ throw new IllegalArgumentException("Association (id=" + association.getId()
+ + ") does not have the device profile required to use at least "
+ + "one of the flags in this transport.");
+ }
+ }
+
+ private TransportUtils() {}
+}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index c6338307b192..f1007e75e0af 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -186,9 +186,28 @@ public class SettingsToPropertiesMapper {
"core_libraries",
"crumpet",
"dck_framework",
+ "desktop_apps",
+ "desktop_better_together",
+ "desktop_bsp",
+ "desktop_camera",
"desktop_connectivity",
+ "desktop_display",
+ "desktop_commercial",
+ "desktop_firmware",
+ "desktop_graphics",
"desktop_hwsec",
+ "desktop_input",
+ "desktop_kernel",
+ "desktop_ml",
+ "desktop_serviceability",
+ "desktop_oobe",
+ "desktop_peripherals",
+ "desktop_pnp",
+ "desktop_security",
"desktop_stats",
+ "desktop_sysui",
+ "desktop_users_and_accounts",
+ "desktop_video",
"desktop_wifi",
"devoptions_settings",
"game",
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index c0a97db7275b..767201125e09 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -361,6 +361,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
case POPULATE_GAME_MODE_SETTINGS: {
removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
final int userId = (int) msg.obj;
+ synchronized (mLock) {
+ if (!mSettings.containsKey(userId)) {
+ GameManagerSettings userSettings = new GameManagerSettings(
+ Environment.getDataSystemDeDirectory(userId));
+ mSettings.put(userId, userSettings);
+ userSettings.readPersistentDataLocked();
+ }
+ }
final String[] packageNames = getInstalledGamePackageNames(userId);
updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
break;
@@ -990,8 +998,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onUserStarting(@NonNull TargetUser user) {
Slog.d(TAG, "Starting user " + user.getUserIdentifier());
- mService.onUserStarting(user,
- Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
+ mService.onUserStarting(user, /*settingDataDirOverride*/ null);
}
@Override
@@ -1596,13 +1603,16 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
- void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
+ void onUserStarting(@NonNull TargetUser user, File settingDataDirOverride) {
final int userId = user.getUserIdentifier();
- synchronized (mLock) {
- if (!mSettings.containsKey(userId)) {
- GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
- mSettings.put(userId, userSettings);
- userSettings.readPersistentDataLocked();
+ if (settingDataDirOverride != null) {
+ synchronized (mLock) {
+ if (!mSettings.containsKey(userId)) {
+ GameManagerSettings userSettings = new GameManagerSettings(
+ settingDataDirOverride);
+ mSettings.put(userId, userSettings);
+ userSettings.readPersistentDataLocked();
+ }
}
}
sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING,
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index c57a1f73d7d7..fd4bf2f3ef65 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -221,9 +221,7 @@ public class GameManagerSettings {
return false;
}
- try {
- final FileInputStream str = mSettingsFile.openRead();
-
+ try (FileInputStream str = mSettingsFile.openRead()) {
final TypedXmlPullParser parser = Xml.resolvePullParser(str);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -251,7 +249,6 @@ public class GameManagerSettings {
+ type);
}
}
- str.close();
} catch (XmlPullParserException | java.io.IOException e) {
Slog.wtf(TAG, "Error reading game manager settings", e);
return false;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6f79f7073b89..b48d0a6ed547 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -15082,11 +15082,13 @@ public class AudioService extends IAudioService.Stub
final String key = "additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis;
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
+ long delayMillis = 0;
+ if (reply.contains(key)) {
+ try {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ } catch (NullPointerException e) {
+ delayMillis = 0;
+ }
}
return delayMillis;
}
@@ -15112,11 +15114,13 @@ public class AudioService extends IAudioService.Stub
final String key = "max_additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis;
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
+ long delayMillis = 0;
+ if (reply.contains(key)) {
+ try {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ } catch (NullPointerException e) {
+ delayMillis = 0;
+ }
}
return delayMillis;
}
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 a1e8f08db0a6..aab2760dbc66 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -122,11 +122,6 @@ public class DisplayManagerFlags {
Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE,
Flags::alwaysRotateDisplayDevice);
- private final FlagState mRefreshRateVotingTelemetry = new FlagState(
- Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
- Flags::refreshRateVotingTelemetry
- );
-
private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
Flags::enablePixelAnisotropyCorrection
@@ -403,10 +398,6 @@ public class DisplayManagerFlags {
return mAlwaysRotateDisplayDevice.isEnabled();
}
- public boolean isRefreshRateVotingTelemetryEnabled() {
- return mRefreshRateVotingTelemetry.isEnabled();
- }
-
public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
return mPixelAnisotropyCorrectionEnabled.isEnabled();
}
@@ -626,7 +617,6 @@ public class DisplayManagerFlags {
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mAlwaysRotateDisplayDevice);
- pw.println(" " + mRefreshRateVotingTelemetry);
pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mIdleScreenRefreshRateTimeout);
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 cc0bbde370fe..8211febade60 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
@@ -191,14 +191,6 @@ flag {
}
flag {
- name: "refresh_rate_voting_telemetry"
- namespace: "display_manager"
- description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
- bug: "310029108"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_pixel_anisotropy_correction"
namespace: "display_manager"
description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
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 1dd4a9b93277..c37733b05fba 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -229,8 +229,7 @@ public class DisplayModeDirector {
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mInjector = injector;
- mVotesStatsReporter = injector.getVotesStatsReporter(
- displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
+ mVotesStatsReporter = injector.getVotesStatsReporter();
mSupportedModesByDisplay = new SparseArray<>();
mAppSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
@@ -3141,7 +3140,7 @@ public class DisplayModeDirector {
SensorManagerInternal getSensorManagerInternal();
@Nullable
- VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled);
+ VotesStatsReporter getVotesStatsReporter();
}
@VisibleForTesting
@@ -3281,10 +3280,9 @@ public class DisplayModeDirector {
}
@Override
- public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ public VotesStatsReporter getVotesStatsReporter() {
// if frame rate override supported, renderRates will be ignored in mode selection
- return new VotesStatsReporter(supportsFrameRateOverride(),
- refreshRateVotingTelemetryEnabled);
+ return new VotesStatsReporter(supportsFrameRateOverride());
}
private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index 7562a525b5f6..7b579c0e0c21 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Trace;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import com.android.internal.util.FrameworkStatsLog;
@@ -36,13 +37,11 @@ class VotesStatsReporter {
private static final String TAG = "VotesStatsReporter";
private static final int REFRESH_RATE_NOT_LIMITED = 1000;
private final boolean mIgnoredRenderRate;
- private final boolean mFrameworkStatsLogReportingEnabled;
- private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1;
+ private final SparseIntArray mLastMinPriorityByDisplay = new SparseIntArray();
- public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
+ VotesStatsReporter(boolean ignoreRenderRate) {
mIgnoredRenderRate = ignoreRenderRate;
- mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
}
void reportVoteChanged(int displayId, int priority, @Nullable Vote vote) {
@@ -57,32 +56,27 @@ class VotesStatsReporter {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
- if (mFrameworkStatsLogReportingEnabled) {
- FrameworkStatsLog.write(
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
- maxRefreshRate, -1);
- }
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+ maxRefreshRate, -1);
}
private void reportVoteRemoved(int displayId, int priority) {
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
- if (mFrameworkStatsLogReportingEnabled) {
- FrameworkStatsLog.write(
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
- }
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
}
void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
SparseArray<Vote> votes) {
- if (!mFrameworkStatsLogReportingEnabled) {
- return;
- }
+ int lastMinPriorityReported = mLastMinPriorityByDisplay.get(
+ displayId, Vote.MAX_PRIORITY + 1);
int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) {
- if (priority < mLastMinPriorityReported && priority < minPriority) {
+ if (priority < lastMinPriorityReported && priority < minPriority) {
continue;
}
Vote vote = votes.get(priority);
@@ -91,7 +85,7 @@ class VotesStatsReporter {
}
// Was previously reported ACTIVE, changed to ADDED
- if (priority >= mLastMinPriorityReported && priority < minPriority) {
+ if (priority >= lastMinPriorityReported && priority < minPriority) {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
FrameworkStatsLog.write(
DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
@@ -99,7 +93,7 @@ class VotesStatsReporter {
maxRefreshRate, selectedRefreshRate);
}
// Was previously reported ADDED, changed to ACTIVE
- if (priority >= minPriority && priority < mLastMinPriorityReported) {
+ if (priority >= minPriority && priority < lastMinPriorityReported) {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
FrameworkStatsLog.write(
DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
@@ -107,7 +101,7 @@ class VotesStatsReporter {
maxRefreshRate, selectedRefreshRate);
}
- mLastMinPriorityReported = minPriority;
+ mLastMinPriorityByDisplay.put(displayId, minPriority);
}
}
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 4505d0e2d799..7e5c1bc9ada5 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -86,3 +86,10 @@ flag {
description: "Enable the time notifications feature, a toggle to enable/disable time-related notifications in Date & Time Settings"
bug: "283267917"
}
+
+flag {
+ name: "certpininstaller_removal"
+ namespace: "network_security"
+ description: "Remove CertPinInstallReceiver from the platform"
+ bug: "391205997"
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index ddace179348c..a04ffdb4951d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -43,7 +43,6 @@ import com.android.internal.annotations.GuardedBy;
import java.util.Collection;
import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class that represents a broker for the endpoint registered by the client app. This class
@@ -89,8 +88,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/** The remote callback interface for this endpoint. */
private final IContextHubEndpointCallback mContextHubEndpointCallback;
- /** True if this endpoint is registered with the service. */
- private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
+ /** True if this endpoint is registered with the service/HAL. */
+ @GuardedBy("mRegistrationLock")
+ private boolean mIsRegistered = false;
+
+ private final Object mRegistrationLock = new Object();
private final Object mOpenSessionLock = new Object();
@@ -192,7 +194,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
public int openSession(HubEndpointInfo destination, String serviceDescriptor)
throws RemoteException {
super.openSession_enforcePermission();
- if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
if (!hasEndpointPermissions(destination)) {
throw new SecurityException(
"Insufficient permission to open a session with endpoint: " + destination);
@@ -223,7 +225,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void closeSession(int sessionId, int reason) throws RemoteException {
super.closeSession_enforcePermission();
- if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
if (!cleanupSessionResources(sessionId)) {
throw new IllegalArgumentException(
"Unknown session ID in closeSession: id=" + sessionId);
@@ -235,19 +237,26 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void unregister() {
super.unregister_enforcePermission();
- mIsRegistered.set(false);
- try {
- mHubInterface.unregisterEndpoint(mHalEndpointInfo);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
- }
synchronized (mOpenSessionLock) {
// Iterate in reverse since cleanupSessionResources will remove the entry
for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
int id = mSessionInfoMap.keyAt(i);
+ halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
cleanupSessionResources(id);
}
}
+ synchronized (mRegistrationLock) {
+ if (!isRegistered()) {
+ Log.w(TAG, "Attempting to unregister when already unregistered");
+ return;
+ }
+ mIsRegistered = false;
+ try {
+ mHubInterface.unregisterEndpoint(mHalEndpointInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
+ }
+ }
mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
releaseWakeLockOnExit();
}
@@ -335,7 +344,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/** Invoked when the underlying binder of this broker has died at the client process. */
@Override
public void binderDied() {
- if (mIsRegistered.get()) {
+ if (isRegistered()) {
unregister();
}
}
@@ -365,6 +374,22 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
+ /**
+ * Registers this endpoints with the Context Hub HAL.
+ *
+ * @throws RemoteException if the registrations fails with a RemoteException
+ */
+ /* package */ void register() throws RemoteException {
+ synchronized (mRegistrationLock) {
+ if (isRegistered()) {
+ Log.w(TAG, "Attempting to register when already registered");
+ } else {
+ mHubInterface.registerEndpoint(mHalEndpointInfo);
+ mIsRegistered = true;
+ }
+ }
+ }
+
/* package */ void attachDeathRecipient() throws RemoteException {
if (mContextHubEndpointCallback != null) {
mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
@@ -425,6 +450,24 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
+ /* package */ void onHalRestart() {
+ synchronized (mRegistrationLock) {
+ mIsRegistered = false;
+ try {
+ register();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
+ }
+ }
+ synchronized (mOpenSessionLock) {
+ for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
+ int id = mSessionInfoMap.keyAt(i);
+ onCloseEndpointSession(id, Reason.HUB_RESET);
+ }
+ }
+ // TODO(b/390029594): Cancel any ongoing reliable communication transactions
+ }
+
private Optional<Byte> onEndpointSessionOpenRequestInternal(
int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
if (!hasEndpointPermissions(initiator)) {
@@ -553,7 +596,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
private void acquireWakeLock() {
Binder.withCleanCallingIdentity(
() -> {
- if (mIsRegistered.get()) {
+ if (isRegistered()) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
}
});
@@ -608,4 +651,10 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
return true;
}
+
+ private boolean isRegistered() {
+ synchronized (mRegistrationLock) {
+ return mIsRegistered;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index ed98bf98f7b7..06aeb62a28b8 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -206,12 +206,6 @@ import java.util.function.Consumer;
EndpointInfo halEndpointInfo =
ContextHubServiceUtil.createHalEndpointInfo(
pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
- try {
- mHubInterface.registerEndpoint(halEndpointInfo);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
- throw e;
- }
broker =
new ContextHubEndpointBroker(
mContext,
@@ -222,6 +216,7 @@ import java.util.function.Consumer;
packageName,
attributionTag,
mTransactionManager);
+ broker.register();
mEndpointMap.put(endpointId, broker);
try {
@@ -282,6 +277,14 @@ import java.util.function.Consumer;
mEndpointMap.remove(endpointId);
}
+ /** Invoked by the service when the Context Hub HAL restarts. */
+ /* package */ void onHalRestart() {
+ for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+ // The broker will close existing sessions and re-register itself
+ broker.onHalRestart();
+ }
+ }
+
@Override
public void onEndpointSessionOpenRequest(
int sessionId,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2b0ca145372b..502a7aeba258 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -259,6 +259,9 @@ public class ContextHubService extends IContextHubService.Stub {
if (mHubInfoRegistry != null) {
mHubInfoRegistry.onHalRestart();
}
+ if (mEndpointManager != null) {
+ mEndpointManager.onHalRestart();
+ }
resetSettings();
if (Flags.reconnectHostEndpointsAfterHalRestart()) {
mClientManager.forEachClientOfHub(mContextHubId,
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index e2889fa9cbf6..18bccd8411d7 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -91,7 +91,7 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor
updateAttributes = true;
}
if (restrictAudioAttributesAlarm()
- && record.getNotification().category != CATEGORY_ALARM
+ && !CATEGORY_ALARM.equals(record.getNotification().category)
&& attributes.getUsage() == AudioAttributes.USAGE_ALARM) {
updateAttributes = true;
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index dc1f93664f79..f060e4d11e82 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -17,8 +17,8 @@
package com.android.server.security;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_BOOT_STATE;
-import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN;
@@ -47,12 +47,8 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
-import org.json.JSONObject;
-
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
@@ -60,7 +56,6 @@ import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
-import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
@@ -69,7 +64,6 @@ import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -126,6 +120,7 @@ class AttestationVerificationPeerDeviceVerifier {
private final LocalDate mTestLocalPatchDate;
private final CertificateFactory mCertificateFactory;
private final CertPathValidator mCertPathValidator;
+ private final CertificateRevocationStatusManager mCertificateRevocationStatusManager;
private final DumpLogger mDumpLogger;
AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
@@ -135,6 +130,7 @@ class AttestationVerificationPeerDeviceVerifier {
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = getTrustAnchors();
+ mCertificateRevocationStatusManager = new CertificateRevocationStatusManager(mContext);
mRevocationEnabled = true;
mTestSystemDate = null;
mTestLocalPatchDate = null;
@@ -150,6 +146,7 @@ class AttestationVerificationPeerDeviceVerifier {
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = trustAnchors;
+ mCertificateRevocationStatusManager = new CertificateRevocationStatusManager(mContext);
mRevocationEnabled = revocationEnabled;
mTestSystemDate = systemDate;
mTestLocalPatchDate = localPatchDate;
@@ -300,15 +297,14 @@ class AttestationVerificationPeerDeviceVerifier {
CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
PKIXParameters validationParams = new PKIXParameters(mTrustAnchors);
+ // Do not use built-in revocation status checker.
+ validationParams.setRevocationEnabled(false);
+ mCertPathValidator.validate(certificatePath, validationParams);
if (mRevocationEnabled) {
// Checks Revocation Status List based on
// https://developer.android.com/training/articles/security-key-attestation#certificate_status
- PKIXCertPathChecker checker = new AndroidRevocationStatusListChecker();
- validationParams.addCertPathChecker(checker);
+ mCertificateRevocationStatusManager.checkRevocationStatus(certificates);
}
- // Do not use built-in revocation status checker.
- validationParams.setRevocationEnabled(false);
- mCertPathValidator.validate(certificatePath, validationParams);
}
private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
@@ -574,96 +570,6 @@ class AttestationVerificationPeerDeviceVerifier {
<= maxPatchLevelDiffMonths;
}
- /**
- * Checks certificate revocation status.
- *
- * Queries status list from android.googleapis.com/attestation/status and checks for
- * the existence of certificate's serial number. If serial number exists in map, then fail.
- */
- private final class AndroidRevocationStatusListChecker extends PKIXCertPathChecker {
- private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
- private static final String STATUS_PROPERTY_KEY = "status";
- private static final String REASON_PROPERTY_KEY = "reason";
- private String mStatusUrl;
- private JSONObject mJsonStatusMap;
-
- @Override
- public void init(boolean forward) throws CertPathValidatorException {
- mStatusUrl = getRevocationListUrl();
- if (mStatusUrl == null || mStatusUrl.isEmpty()) {
- throw new CertPathValidatorException(
- "R.string.vendor_required_attestation_revocation_list_url is empty.");
- }
- // TODO(b/221067843): Update to only pull status map on non critical path and if
- // out of date (24hrs).
- mJsonStatusMap = getStatusMap(mStatusUrl);
- }
-
- @Override
- public boolean isForwardCheckingSupported() {
- return false;
- }
-
- @Override
- public Set<String> getSupportedExtensions() {
- return null;
- }
-
- @Override
- public void check(Certificate cert, Collection<String> unresolvedCritExts)
- throws CertPathValidatorException {
- X509Certificate x509Certificate = (X509Certificate) cert;
- // The json key is the certificate's serial number converted to lowercase hex.
- String serialNumber = x509Certificate.getSerialNumber().toString(16);
-
- if (serialNumber == null) {
- throw new CertPathValidatorException("Certificate serial number can not be null.");
- }
-
- if (mJsonStatusMap.has(serialNumber)) {
- JSONObject revocationStatus;
- String status;
- String reason;
- try {
- revocationStatus = mJsonStatusMap.getJSONObject(serialNumber);
- status = revocationStatus.getString(STATUS_PROPERTY_KEY);
- reason = revocationStatus.getString(REASON_PROPERTY_KEY);
- } catch (Throwable t) {
- throw new CertPathValidatorException("Unable get properties for certificate "
- + "with serial number " + serialNumber);
- }
- throw new CertPathValidatorException(
- "Invalid certificate with serial number " + serialNumber
- + " has status " + status
- + " because reason " + reason);
- }
- }
-
- private JSONObject getStatusMap(String stringUrl) throws CertPathValidatorException {
- URL url;
- try {
- url = new URL(stringUrl);
- } catch (Throwable t) {
- throw new CertPathValidatorException(
- "Unable to get revocation status from " + mStatusUrl, t);
- }
-
- try (InputStream inputStream = url.openStream()) {
- JSONObject statusListJson = new JSONObject(
- new String(inputStream.readAllBytes(), UTF_8));
- return statusListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
- } catch (Throwable t) {
- throw new CertPathValidatorException(
- "Unable to parse revocation status from " + mStatusUrl, t);
- }
- }
-
- private String getRevocationListUrl() {
- return mContext.getResources().getString(
- R.string.vendor_required_attestation_revocation_list_url);
- }
- }
-
/* Mutable data class for tracking dump data from verifications. */
private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
new file mode 100644
index 000000000000..d36d9f5f6636
--- /dev/null
+++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2025 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.security;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Environment;
+import android.os.PersistableBundle;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.X509Certificate;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Manages the revocation status of certificates used in remote attestation. */
+class CertificateRevocationStatusManager {
+ private static final String TAG = "AVF_CRL";
+ // Must be unique within system server
+ private static final int JOB_ID = 1737671340;
+ private static final String REVOCATION_STATUS_FILE_NAME = "certificate_revocation_status.txt";
+ private static final String REVOCATION_STATUS_FILE_FIELD_DELIMITER = ",";
+
+ /**
+ * The number of days since last update for which a stored revocation status can be accepted.
+ */
+ @VisibleForTesting static final int MAX_DAYS_SINCE_LAST_CHECK = 30;
+
+ /**
+ * The number of days since issue date for an intermediary certificate to be considered fresh
+ * and not require a revocation list check.
+ */
+ private static final int FRESH_INTERMEDIARY_CERT_DAYS = 70;
+
+ /**
+ * The expected number of days between a certificate's issue date and notBefore date. Used to
+ * infer a certificate's issue date from its notBefore date.
+ */
+ private static final int DAYS_BETWEEN_ISSUE_AND_NOT_BEFORE_DATES = 2;
+
+ private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
+ private static final Object sFileLock = new Object();
+
+ private final Context mContext;
+ private final String mTestRemoteRevocationListUrl;
+ private final File mTestRevocationStatusFile;
+ private final boolean mShouldScheduleJob;
+
+ CertificateRevocationStatusManager(Context context) {
+ this(context, null, null, true);
+ }
+
+ @VisibleForTesting
+ CertificateRevocationStatusManager(
+ Context context,
+ String testRemoteRevocationListUrl,
+ File testRevocationStatusFile,
+ boolean shouldScheduleJob) {
+ mContext = context;
+ mTestRemoteRevocationListUrl = testRemoteRevocationListUrl;
+ mTestRevocationStatusFile = testRevocationStatusFile;
+ mShouldScheduleJob = shouldScheduleJob;
+ }
+
+ /**
+ * Check the revocation status of the provided {@link X509Certificate}s.
+ *
+ * <p>The provided certificates should have been validated and ordered from leaf to a
+ * certificate issued by the trust anchor, per the convention specified in the javadoc of {@link
+ * java.security.cert.CertPath}.
+ *
+ * @param certificates List of certificates to be checked
+ * @throws CertPathValidatorException if the check failed
+ */
+ void checkRevocationStatus(List<X509Certificate> certificates)
+ throws CertPathValidatorException {
+ if (!needToCheckRevocationStatus(certificates)) {
+ return;
+ }
+ List<String> serialNumbers = new ArrayList<>();
+ for (X509Certificate certificate : certificates) {
+ String serialNumber = certificate.getSerialNumber().toString(16);
+ if (serialNumber == null) {
+ throw new CertPathValidatorException("Certificate serial number cannot be null.");
+ }
+ serialNumbers.add(serialNumber);
+ }
+ try {
+ JSONObject revocationList = fetchRemoteRevocationList();
+ Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+ for (String serialNumber : serialNumbers) {
+ areCertificatesRevoked.put(serialNumber, revocationList.has(serialNumber));
+ }
+ updateLastRevocationCheckData(areCertificatesRevoked);
+ for (Map.Entry<String, Boolean> entry : areCertificatesRevoked.entrySet()) {
+ if (entry.getValue()) {
+ throw new CertPathValidatorException(
+ "Certificate " + entry.getKey() + " has been revoked.");
+ }
+ }
+ } catch (IOException | JSONException ex) {
+ Slog.d(TAG, "Fallback to check stored revocation status", ex);
+ if (ex instanceof IOException && mShouldScheduleJob) {
+ scheduleJobToUpdateStoredDataWithRemoteRevocationList(serialNumbers);
+ }
+ for (X509Certificate certificate : certificates) {
+ // Assume recently issued certificates are not revoked.
+ if (isIssuedWithinDays(certificate, MAX_DAYS_SINCE_LAST_CHECK)) {
+ String serialNumber = certificate.getSerialNumber().toString(16);
+ serialNumbers.remove(serialNumber);
+ }
+ }
+ Map<String, LocalDateTime> lastRevocationCheckData;
+ try {
+ lastRevocationCheckData = getLastRevocationCheckData();
+ } catch (IOException ex2) {
+ throw new CertPathValidatorException(
+ "Unable to load stored revocation status", ex2);
+ }
+ for (String serialNumber : serialNumbers) {
+ if (!lastRevocationCheckData.containsKey(serialNumber)
+ || lastRevocationCheckData
+ .get(serialNumber)
+ .isBefore(
+ LocalDateTime.now().minusDays(MAX_DAYS_SINCE_LAST_CHECK))) {
+ throw new CertPathValidatorException(
+ "Unable to verify the revocation status of certificate "
+ + serialNumber);
+ }
+ }
+ }
+ }
+
+ private static boolean needToCheckRevocationStatus(
+ List<X509Certificate> certificatesOrderedLeafFirst) {
+ if (certificatesOrderedLeafFirst.isEmpty()) {
+ return false;
+ }
+ // A certificate isn't revoked when it is first issued, so we treat it as checked on its
+ // issue date.
+ if (!isIssuedWithinDays(certificatesOrderedLeafFirst.get(0), MAX_DAYS_SINCE_LAST_CHECK)) {
+ return true;
+ }
+ for (int i = 1; i < certificatesOrderedLeafFirst.size(); i++) {
+ if (!isIssuedWithinDays(
+ certificatesOrderedLeafFirst.get(i), FRESH_INTERMEDIARY_CERT_DAYS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isIssuedWithinDays(X509Certificate certificate, int days) {
+ LocalDate notBeforeDate =
+ LocalDate.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault());
+ LocalDate expectedIssueData =
+ notBeforeDate.plusDays(DAYS_BETWEEN_ISSUE_AND_NOT_BEFORE_DATES);
+ return LocalDate.now().minusDays(days + 1).isBefore(expectedIssueData);
+ }
+
+ void updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+ JSONObject revocationList, Collection<String> otherCertificatesToCheck) {
+ Set<String> allCertificatesToCheck = new HashSet<>(otherCertificatesToCheck);
+ try {
+ allCertificatesToCheck.addAll(getLastRevocationCheckData().keySet());
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to update last check date of stored data.", ex);
+ }
+ Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+ for (String serialNumber : allCertificatesToCheck) {
+ areCertificatesRevoked.put(serialNumber, revocationList.has(serialNumber));
+ }
+ updateLastRevocationCheckData(areCertificatesRevoked);
+ }
+
+ /**
+ * Update the last revocation check data stored on this device.
+ *
+ * @param areCertificatesRevoked A Map whose keys are certificate serial numbers and values are
+ * whether that certificate has been revoked
+ */
+ void updateLastRevocationCheckData(Map<String, Boolean> areCertificatesRevoked) {
+ LocalDateTime now = LocalDateTime.now();
+ synchronized (sFileLock) {
+ Map<String, LocalDateTime> lastRevocationCheckData;
+ try {
+ lastRevocationCheckData = getLastRevocationCheckData();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to updateLastRevocationCheckData", ex);
+ return;
+ }
+ for (Map.Entry<String, Boolean> entry : areCertificatesRevoked.entrySet()) {
+ if (entry.getValue()) {
+ lastRevocationCheckData.remove(entry.getKey());
+ } else {
+ lastRevocationCheckData.put(entry.getKey(), now);
+ }
+ }
+ storeLastRevocationCheckData(lastRevocationCheckData);
+ }
+ }
+
+ Map<String, LocalDateTime> getLastRevocationCheckData() throws IOException {
+ Map<String, LocalDateTime> data = new HashMap<>();
+ File dataFile = getLastRevocationCheckDataFile();
+ synchronized (sFileLock) {
+ if (!dataFile.exists()) {
+ return data;
+ }
+ String dataString;
+ try (FileInputStream in = new FileInputStream(dataFile)) {
+ dataString = new String(in.readAllBytes(), UTF_8);
+ }
+ for (String line : dataString.split(System.lineSeparator())) {
+ String[] elements = line.split(REVOCATION_STATUS_FILE_FIELD_DELIMITER);
+ if (elements.length != 2) {
+ continue;
+ }
+ try {
+ data.put(elements[0], LocalDateTime.parse(elements[1]));
+ } catch (DateTimeParseException ex) {
+ Slog.e(
+ TAG,
+ "Unable to parse last checked LocalDateTime from file. Deleting the"
+ + " potentially corrupted file.",
+ ex);
+ dataFile.delete();
+ return data;
+ }
+ }
+ }
+ return data;
+ }
+
+ @VisibleForTesting
+ void storeLastRevocationCheckData(Map<String, LocalDateTime> lastRevocationCheckData) {
+ StringBuilder dataStringBuilder = new StringBuilder();
+ for (Map.Entry<String, LocalDateTime> entry : lastRevocationCheckData.entrySet()) {
+ dataStringBuilder
+ .append(entry.getKey())
+ .append(REVOCATION_STATUS_FILE_FIELD_DELIMITER)
+ .append(entry.getValue())
+ .append(System.lineSeparator());
+ }
+ synchronized (sFileLock) {
+ try (FileOutputStream fileOutputStream =
+ new FileOutputStream(getLastRevocationCheckDataFile())) {
+ fileOutputStream.write(dataStringBuilder.toString().getBytes(UTF_8));
+ Slog.d(TAG, "Successfully stored revocation status data.");
+ } catch (IOException ex) {
+ Slog.e(TAG, "Failed to store revocation status data.", ex);
+ }
+ }
+ }
+
+ private File getLastRevocationCheckDataFile() {
+ if (mTestRevocationStatusFile != null) {
+ return mTestRevocationStatusFile;
+ }
+ return new File(Environment.getDataSystemDirectory(), REVOCATION_STATUS_FILE_NAME);
+ }
+
+ private void scheduleJobToUpdateStoredDataWithRemoteRevocationList(List<String> serialNumbers) {
+ JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Slog.e(TAG, "Unable to get job scheduler.");
+ return;
+ }
+ Slog.d(TAG, "Scheduling job to fetch remote CRL.");
+ PersistableBundle extras = new PersistableBundle();
+ extras.putStringArray(
+ UpdateCertificateRevocationStatusJobService.EXTRA_KEY_CERTIFICATES_TO_CHECK,
+ serialNumbers.toArray(new String[0]));
+ jobScheduler.schedule(
+ new JobInfo.Builder(
+ JOB_ID,
+ new ComponentName(
+ mContext,
+ UpdateCertificateRevocationStatusJobService.class))
+ .setExtras(extras)
+ .setRequiredNetwork(
+ new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build())
+ .build());
+ }
+
+ /**
+ * Fetches the revocation list from the URL specified in
+ * R.string.vendor_required_attestation_revocation_list_url
+ *
+ * @return The remote revocation list entries in a JSONObject
+ * @throws CertPathValidatorException if the URL is not defined or is malformed.
+ * @throws IOException if the URL is valid but the fetch failed.
+ * @throws JSONException if the revocation list content cannot be parsed
+ */
+ JSONObject fetchRemoteRevocationList()
+ throws CertPathValidatorException, IOException, JSONException {
+ String urlString = getRemoteRevocationListUrl();
+ if (urlString == null || urlString.isEmpty()) {
+ throw new CertPathValidatorException(
+ "R.string.vendor_required_attestation_revocation_list_url is empty.");
+ }
+ URL url;
+ try {
+ url = new URL(urlString);
+ } catch (MalformedURLException ex) {
+ throw new CertPathValidatorException("Unable to parse the URL " + urlString, ex);
+ }
+ byte[] revocationListBytes;
+ try (InputStream inputStream = url.openStream()) {
+ revocationListBytes = inputStream.readAllBytes();
+ }
+ JSONObject revocationListJson = new JSONObject(new String(revocationListBytes, UTF_8));
+ return revocationListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
+ }
+
+ private String getRemoteRevocationListUrl() {
+ if (mTestRemoteRevocationListUrl != null) {
+ return mTestRemoteRevocationListUrl;
+ }
+ return mContext.getResources()
+ .getString(R.string.vendor_required_attestation_revocation_list_url);
+ }
+}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index fa4bf228c683..7a31a0006bb9 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -3,5 +3,6 @@
include /core/java/android/security/OWNERS
per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
+per-file *CertificateRevocationStatus* = file:/core/java/android/security/attestationverification/OWNERS
per-file FileIntegrity*.java = victorhsieh@google.com
per-file KeyChainSystemService.java = file:platform/packages/apps/KeyChain:/OWNERS
diff --git a/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java b/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java
new file mode 100644
index 000000000000..768c812f47a3
--- /dev/null
+++ b/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 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.security;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** A {@link JobService} that fetches the certificate revocation list from a remote location. */
+public class UpdateCertificateRevocationStatusJobService extends JobService {
+
+ static final String EXTRA_KEY_CERTIFICATES_TO_CHECK =
+ "com.android.server.security.extra.CERTIFICATES_TO_CHECK";
+ private static final String TAG = "AVF_CRL";
+ private ExecutorService mExecutorService;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mExecutorService = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ mExecutorService.execute(
+ () -> {
+ try {
+ CertificateRevocationStatusManager certificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(this);
+ Slog.d(TAG, "Starting to fetch remote CRL from job service.");
+ JSONObject revocationList =
+ certificateRevocationStatusManager.fetchRemoteRevocationList();
+ String[] certificatesToCheckFromJobParams =
+ params.getExtras().getStringArray(EXTRA_KEY_CERTIFICATES_TO_CHECK);
+ if (certificatesToCheckFromJobParams == null) {
+ Slog.e(TAG, "Extras not found: " + EXTRA_KEY_CERTIFICATES_TO_CHECK);
+ return;
+ }
+ certificateRevocationStatusManager
+ .updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+ revocationList,
+ Arrays.asList(certificatesToCheckFromJobParams));
+ } catch (Throwable t) {
+ Slog.e(TAG, "Unable to update the stored revocation status.", t);
+ }
+ jobFinished(params, false);
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mExecutorService.shutdown();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 20917ba21439..cf9c57aa634a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -113,7 +113,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.hasWindowExtensionsEnabled;
@@ -247,7 +246,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -659,8 +657,6 @@ final class ActivityRecord extends WindowToken {
private RemoteAnimationDefinition mRemoteAnimationDefinition;
- AnimatingActivityRegistry mAnimatingActivityRegistry;
-
// Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task
// due to picture-in-picture. This gets cleared whenever this activity or the Task
// it references to gets removed. This should also be cleared when we move out of pip.
@@ -857,12 +853,6 @@ final class ActivityRecord extends WindowToken {
})
@interface SplashScreenBehavior { }
- // Force an app transition to be ran in the case the visibility of the app did not change.
- // We use this for the case of moving a Root Task to the back with multiple activities, and the
- // top activity enters PIP; the bottom activity's visibility stays the same, but we need to
- // run the transition.
- boolean mRequestForceTransition;
-
boolean mEnteringAnimation;
boolean mOverrideTaskTransition;
boolean mDismissKeyguardIfInsecure;
@@ -1587,9 +1577,6 @@ final class ActivityRecord extends WindowToken {
}
}
final Task rootTask = getRootTask();
-
- updateAnimatingActivityRegistry();
-
if (task == mLastParentBeforePip && task != null) {
// Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
@@ -1691,20 +1678,6 @@ final class ActivityRecord extends WindowToken {
return !organizedTaskFragment.isAllowedToEmbedActivityInTrustedMode(this);
}
- void updateAnimatingActivityRegistry() {
- final Task rootTask = getRootTask();
- final AnimatingActivityRegistry registry = rootTask != null
- ? rootTask.getAnimatingActivityRegistry()
- : null;
-
- // If we reparent, make sure to remove ourselves from the old animation registry.
- if (mAnimatingActivityRegistry != null && mAnimatingActivityRegistry != registry) {
- mAnimatingActivityRegistry.notifyFinished(this);
- }
-
- mAnimatingActivityRegistry = registry;
- }
-
boolean canAutoEnterPip() {
// beforeStopping=false since the actual pip-ing will take place after startPausing()
final boolean activityCanPip = checkEnterPictureInPictureState(
@@ -1789,7 +1762,6 @@ final class ActivityRecord extends WindowToken {
if (prevDc.mOpeningApps.remove(this)) {
// Transfer opening transition to new display.
mDisplayContent.mOpeningApps.add(this);
- mDisplayContent.transferAppTransitionFrom(prevDc);
mDisplayContent.executeAppTransition();
}
@@ -4632,12 +4604,6 @@ final class ActivityRecord extends WindowToken {
}
}
- // In this case, the starting icon has already been displayed, so start
- // letting windows get shown immediately without any more transitions.
- if (fromActivity.mVisible) {
- mDisplayContent.mSkipAppTransitionAnimation = true;
- }
-
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s"
+ " from %s to %s", tStartingWindow, fromActivity, this);
@@ -5668,76 +5634,17 @@ final class ActivityRecord extends WindowToken {
mTransitionController.mValidateCommitVis.add(this);
return;
}
- // If we are preparing an app transition, then delay changing
- // the visibility of this token until we execute that transition.
- if (deferCommitVisibilityChange(visible)) {
- return;
- }
commitVisibility(visible, true /* performLayout */);
updateReportedVisibilityLocked();
}
- /**
- * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
- * Then its visibility will be committed until the transition is ready.
- */
- private boolean deferCommitVisibilityChange(boolean visible) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- // Shell transition doesn't use opening/closing sets.
- return false;
- }
- if (!mDisplayContent.mAppTransition.isTransitionSet()) {
- return false;
- }
- if (mWaitForEnteringPinnedMode && mVisible == visible) {
- // If the visibility is not changed during enter PIP, we don't want to include it in
- // app transition to affect the animation theme, because the Pip organizer will
- // animate the entering PIP instead.
- return false;
- }
-
- // The animation will be visible soon so do not skip by screen off.
- final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
- .isKeyguardGoingAway(mDisplayContent.mDisplayId);
- // Ignore display frozen so the opening / closing transition type can be updated correctly
- // even if the display is frozen. And it's safe since in applyAnimation will still check
- // DC#okToAnimate again if the transition animation is fine to apply.
- if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
- return false;
- }
- if (visible) {
- mDisplayContent.mOpeningApps.add(this);
- mEnteringAnimation = true;
- } else if (mVisible) {
- mDisplayContent.mClosingApps.add(this);
- mEnteringAnimation = false;
- }
- if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
- // Add the launching-behind activity to mOpeningApps.
- final WindowState win = mDisplayContent.findFocusedWindow();
- if (win != null) {
- final ActivityRecord focusedActivity = win.mActivityRecord;
- if (focusedActivity != null) {
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
- focusedActivity);
- // Force animation to be loaded.
- mDisplayContent.mOpeningApps.add(focusedActivity);
- }
- }
- }
- return true;
- }
-
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return false;
}
- // If it was set to true, reset the last request to force the transition.
- mRequestForceTransition = false;
return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
}
@@ -5900,27 +5807,6 @@ final class ActivityRecord extends WindowToken {
}
/**
- * Check if visibility of this {@link ActivityRecord} should be updated as part of an app
- * transition.
- *
- * <p class="note><strong>Note:</strong> If the visibility of this {@link ActivityRecord} is
- * already set to {@link #mVisible}, we don't need to update the visibility. So {@code false} is
- * returned.</p>
- *
- * @param visible {@code true} if this {@link ActivityRecord} should become visible,
- * {@code false} if this should become invisible.
- * @return {@code true} if visibility of this {@link ActivityRecord} should be updated, and
- * an app transition animation should be run.
- */
- boolean shouldApplyAnimation(boolean visible) {
- // Allow for state update and animation to be applied if:
- // * activity is transitioning visibility state
- // * or the activity was marked as hidden and is exiting before we had a chance to play the
- // transition animation
- return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting);
- }
-
- /**
* See {@link Activity#setRecentsScreenshotEnabled}.
*/
void setRecentsScreenshotEnabled(boolean enabled) {
@@ -6208,13 +6094,8 @@ final class ActivityRecord extends WindowToken {
return false;
}
- // Hide all activities on the presenting display so that malicious apps can't do tap
- // jacking (b/391466268).
- // For now, this should only be applied to external displays because presentations can only
- // be shown on them.
- // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
- // the presentation won't stop its controlling activity.
- if (enablePresentationForConnectedDisplays() && mDisplayContent.mIsPresenting) {
+ // A presentation stopps all activities behind on the same display.
+ if (mWmService.mPresentationController.shouldOccludeActivities(getDisplayId())) {
return false;
}
@@ -7635,13 +7516,6 @@ final class ActivityRecord extends WindowToken {
}
@Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- return mAnimatingActivityRegistry != null
- && mAnimatingActivityRegistry.notifyAboutToFinish(
- this, endDeferFinishCallback);
- }
-
- @Override
boolean isWaitingForTransitionStart() {
final DisplayContent dc = getDisplayContent();
return dc != null && dc.mAppTransition.isTransitionSet()
@@ -7662,10 +7536,6 @@ final class ActivityRecord extends WindowToken {
@Override
public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
- if (mAnimatingActivityRegistry != null) {
- mAnimatingActivityRegistry.notifyStarting(this);
- }
-
if (mNeedsLetterboxedAnimation) {
updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
mNeedsAnimationBoundsLayer = true;
@@ -7676,17 +7546,7 @@ final class ActivityRecord extends WindowToken {
// new layer.
if (mNeedsAnimationBoundsLayer) {
mTmpRect.setEmpty();
- if (getDisplayContent().mAppTransitionController.isTransitWithinTask(
- getTransit(), task)) {
- task.getBounds(mTmpRect);
- } else {
- final Task rootTask = getRootTask();
- if (rootTask == null) {
- return;
- }
- // Set clip rect to root task bounds.
- rootTask.getBounds(mTmpRect);
- }
+ task.getBounds(mTmpRect);
mAnimationBoundsLayer = createAnimationBoundsLayer(t);
// Crop to root task bounds.
@@ -7842,10 +7702,6 @@ final class ActivityRecord extends WindowToken {
mNeedsLetterboxedAnimation = false;
updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
}
-
- if (mAnimatingActivityRegistry != null) {
- mAnimatingActivityRegistry.notifyFinished(this);
- }
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 254127dee7a8..819e117e6d05 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4128,22 +4128,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void registerRemoteAnimationsForDisplay(int displayId,
RemoteAnimationDefinition definition) {
- mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
- "registerRemoteAnimations");
- definition.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
- synchronized (mGlobalLock) {
- final DisplayContent display = mRootWindowContainer.getDisplayContent(displayId);
- if (display == null) {
- Slog.e(TAG, "Couldn't find display with id: " + displayId);
- return;
- }
- final long origId = Binder.clearCallingIdentity();
- try {
- display.registerRemoteAnimations(definition);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
+ // TODO(b/365884835): Remove callers.
}
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
diff --git a/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java b/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java
deleted file mode 100644
index 18ec96c38264..000000000000
--- a/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Keeps track of all {@link ActivityRecord} that are animating and makes sure all animations are
- * finished at the same time such that we don't run into issues with z-ordering: An activity A
- * that has a shorter animation that is above another activity B with a longer animation in the same
- * task, the animation layer would put the B on top of A, but from the hierarchy, A needs to be on
- * top of B. Thus, we defer reparenting A to the original hierarchy such that it stays on top of B
- * until B finishes animating.
- */
-class AnimatingActivityRegistry {
-
- private ArraySet<ActivityRecord> mAnimatingActivities = new ArraySet<>();
- private ArrayMap<ActivityRecord, Runnable> mFinishedTokens = new ArrayMap<>();
-
- private ArrayList<Runnable> mTmpRunnableList = new ArrayList<>();
-
- private boolean mEndingDeferredFinish;
-
- /**
- * Notifies that an {@link ActivityRecord} has started animating.
- */
- void notifyStarting(ActivityRecord token) {
- mAnimatingActivities.add(token);
- }
-
- /**
- * Notifies that an {@link ActivityRecord} has finished animating.
- */
- void notifyFinished(ActivityRecord activity) {
- mAnimatingActivities.remove(activity);
- mFinishedTokens.remove(activity);
-
- // If we were the last activity, make sure the end all deferred finishes.
- if (mAnimatingActivities.isEmpty()) {
- endDeferringFinished();
- }
- }
-
- /**
- * Called when an {@link ActivityRecord} is about to finish animating.
- *
- * @param endDeferFinishCallback Callback to run when defer finish should be ended.
- * @return {@code true} if finishing the animation should be deferred, {@code false} otherwise.
- */
- boolean notifyAboutToFinish(ActivityRecord activity, Runnable endDeferFinishCallback) {
- final boolean removed = mAnimatingActivities.remove(activity);
- if (!removed) {
- return false;
- }
-
- if (mAnimatingActivities.isEmpty()) {
-
- // If no animations are animating anymore, finish all others.
- endDeferringFinished();
- return false;
- } else {
-
- // Otherwise let's put it into the pending list of to be finished animations.
- mFinishedTokens.put(activity, endDeferFinishCallback);
- return true;
- }
- }
-
- private void endDeferringFinished() {
-
- // Don't start recursing. Running the finished listener invokes notifyFinished, which may
- // invoked us again.
- if (mEndingDeferredFinish) {
- return;
- }
- try {
- mEndingDeferredFinish = true;
-
- // Copy it into a separate temp list to avoid modifying the collection while iterating
- // as calling the callback may call back into notifyFinished.
- for (int i = mFinishedTokens.size() - 1; i >= 0; i--) {
- mTmpRunnableList.add(mFinishedTokens.valueAt(i));
- }
- mFinishedTokens.clear();
- for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) {
- mTmpRunnableList.get(i).run();
- }
- mTmpRunnableList.clear();
- } finally {
- mEndingDeferredFinish = false;
- }
- }
-
- void dump(PrintWriter pw, String header, String prefix) {
- if (!mAnimatingActivities.isEmpty() || !mFinishedTokens.isEmpty()) {
- pw.print(prefix); pw.println(header);
- prefix = prefix + " ";
- pw.print(prefix); pw.print("mAnimatingActivities="); pw.println(mAnimatingActivities);
- pw.print(prefix); pw.print("mFinishedTokens="); pw.println(mFinishedTokens);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 3dc377dbc14c..4458ed76dcba 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -109,15 +109,6 @@ public interface AnimationAdapter {
* Gets called when the animation is about to finish and gives the client the opportunity to
* defer finishing the animation, i.e. it keeps the leash around until the client calls
* endDeferFinishCallback.
- * <p>
- * This has the same effect as
- * {@link com.android.server.wm.SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}
- * . The later will be evaluated first and has precedence over this method if it returns true,
- * which means that if the {@link com.android.server.wm.SurfaceAnimator.Animatable} requests to
- * defer its finish, this method won't be called so this adapter will never have access to the
- * finish callback. On the other hand, if the
- * {@link com.android.server.wm.SurfaceAnimator.Animatable}, doesn't request to defer, this
- * {@link AnimationAdapter} is responsible for ending the animation.
*
* @param endDeferFinishCallback The callback to call when defer finishing should be ended.
* @return Whether the client would like to defer the animation finish.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 932f26857105..9c4b722feb47 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1462,7 +1462,7 @@ public class AppTransition implements Dump {
}
boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
- if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ if (WindowManagerService.sEnableShellTransitions) {
return false;
}
mNextAppTransitionRequests.add(transit);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
deleted file mode 100644
index d5fe056a2ba4..000000000000
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ /dev/null
@@ -1,1352 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static com.android.server.wm.AppTransition.isNormalTransit;
-import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
-import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.Trace;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.WindowManager.TransitionFlags;
-import android.view.WindowManager.TransitionOldType;
-import android.view.WindowManager.TransitionType;
-import android.window.ITaskFragmentOrganizer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Checks for app transition readiness, resolves animation attributes and performs visibility
- * change for apps that animate as part of an app transition.
- */
-public class AppTransitionController {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransitionController" : TAG_WM;
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
- private final WallpaperController mWallpaperControllerLocked;
- private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
-
- private static final int TYPE_NONE = 0;
- private static final int TYPE_ACTIVITY = 1;
- private static final int TYPE_TASK_FRAGMENT = 2;
- private static final int TYPE_TASK = 3;
-
- @IntDef(prefix = { "TYPE_" }, value = {
- TYPE_NONE,
- TYPE_ACTIVITY,
- TYPE_TASK_FRAGMENT,
- TYPE_TASK
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface TransitContainerType {}
-
- private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
- private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
-
- AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
- mService = service;
- mDisplayContent = displayContent;
- mWallpaperControllerLocked = mDisplayContent.mWallpaperController;
- }
-
- void registerRemoteAnimations(RemoteAnimationDefinition definition) {
- mRemoteAnimationDefinition = definition;
- }
-
- /**
- * Returns the currently visible window that is associated with the wallpaper in case we are
- * transitioning from an activity with a wallpaper to one without.
- */
- @Nullable
- private WindowState getOldWallpaper() {
- final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
- final @TransitionType int firstTransit =
- mDisplayContent.mAppTransition.getFirstAppTransition();
-
- final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, true /* visible */);
- final boolean showWallpaper = wallpaperTarget != null
- && (wallpaperTarget.hasWallpaper()
- // Update task open transition to wallpaper transition when wallpaper is visible.
- // (i.e.launching app info activity from recent tasks)
- || ((firstTransit == TRANSIT_OPEN || firstTransit == TRANSIT_TO_FRONT)
- && (!openingWcs.isEmpty() && openingWcs.valueAt(0).asTask() != null)
- && mWallpaperControllerLocked.isWallpaperVisible()));
- // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
- // don't consider upgrading to wallpaper transition.
- return (mWallpaperControllerLocked.isWallpaperTargetAnimating() || !showWallpaper)
- ? null : wallpaperTarget;
- }
-
- /**
- * Handle application transition for given display.
- */
- void handleAppTransitionReady() {
- mTempTransitionReasons.clear();
- if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
- || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
- || !transitionGoodToGoForTaskFragments()) {
- return;
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
- // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause.
- mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow,
- true /* traverseTopToBottom */);
- // TODO(new-app-transition): Remove code using appTransition.getAppTransition()
- final AppTransition appTransition = mDisplayContent.mAppTransition;
-
- mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear();
-
- appTransition.removeAppTransitionTimeoutCallbacks();
-
- mDisplayContent.mWallpaperMayChange = false;
-
- int appCount = mDisplayContent.mOpeningApps.size();
- for (int i = 0; i < appCount; ++i) {
- // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
- // window is removed, or window relayout to invisible. This also affects window
- // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
- // transition selection depends on wallpaper target visibility.
- mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
- }
- appCount = mDisplayContent.mChangingContainers.size();
- for (int i = 0; i < appCount; ++i) {
- // Clearing for same reason as above.
- final ActivityRecord activity = getAppFromContainer(
- mDisplayContent.mChangingContainers.valueAtUnchecked(i));
- if (activity != null) {
- activity.clearAnimatingFlags();
- }
- }
-
- // Adjust wallpaper before we pull the lower/upper target, since pending changes
- // (like the clearAnimatingFlags() above) might affect wallpaper target result.
- // Or, the opening app window should be a wallpaper target.
- mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
- mDisplayContent.mOpeningApps);
-
- ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
- ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
- if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringFinishTransition()) {
- tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
- tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
- }
-
- @TransitionOldType final int transit = getTransitCompatType(
- mDisplayContent.mAppTransition, tmpOpenApps,
- tmpCloseApps, mDisplayContent.mChangingContainers,
- mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
- mDisplayContent.mSkipAppTransitionAnimation);
- mDisplayContent.mSkipAppTransitionAnimation = false;
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "handleAppTransitionReady: displayId=%d appTransition={%s}"
- + " openingApps=[%s] closingApps=[%s] transit=%s",
- mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps,
- tmpCloseApps, AppTransition.appTransitionOldToString(transit));
-
- // Find the layout params of the top-most application window in the tokens, which is
- // what will control the animation theme. If all closing windows are obscured, then there is
- // no need to do an animation. This is the case, for example, when this transition is being
- // done behind a dream window.
- final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps,
- tmpCloseApps, mDisplayContent.mChangingContainers);
- final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
- tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers);
- final ActivityRecord topOpeningApp =
- getTopApp(tmpOpenApps, false /* ignoreHidden */);
- final ActivityRecord topClosingApp =
- getTopApp(tmpCloseApps, false /* ignoreHidden */);
- final ActivityRecord topChangingApp =
- getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
- final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
-
- // Check if there is any override
- if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
- // Unfreeze the windows that were previously frozen for TaskFragment animation.
- overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
- }
-
- final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
- || containsVoiceInteraction(mDisplayContent.mOpeningApps);
-
- final int layoutRedo;
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- try {
- applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction);
- handleClosingApps();
- handleOpeningApps();
- handleChangingApps(transit);
- handleClosingChangingContainers();
-
- appTransition.setLastAppTransition(transit, topOpeningApp,
- topClosingApp, topChangingApp);
-
- final int flags = appTransition.getTransitFlags();
- layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
- appTransition.postAnimationCallback();
- } finally {
- appTransition.clear();
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
-
- mService.mSnapshotController.onTransitionStarting(mDisplayContent);
-
- mDisplayContent.mOpeningApps.clear();
- mDisplayContent.mClosingApps.clear();
- mDisplayContent.mChangingContainers.clear();
- mDisplayContent.mUnknownAppVisibilityController.clear();
- mDisplayContent.mClosingChangingContainers.clear();
-
- // This has changed the visibility of windows, so perform
- // a new layout to get them all up-to-date.
- mDisplayContent.setLayoutNeeded();
-
- mDisplayContent.computeImeTarget(true /* updateImeTarget */);
-
- mService.mAtmService.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
- mTempTransitionReasons);
-
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-
- mDisplayContent.pendingLayoutChanges |=
- layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
- }
-
- /**
- * Get old transit type based on the current transit requests.
- *
- * @param appTransition {@link AppTransition} for managing app transition state.
- * @param openingApps {@link ActivityRecord}s which are becoming visible.
- * @param closingApps {@link ActivityRecord}s which are becoming invisible.
- * @param changingContainers {@link WindowContainer}s which are changed in configuration.
- * @param wallpaperTarget If non-null, this is the currently visible window that is associated
- * with the wallpaper.
- * @param oldWallpaper The currently visible window that is associated with the wallpaper in
- * case we are transitioning from an activity with a wallpaper to one
- * without. Otherwise null.
- */
- @TransitionOldType static int getTransitCompatType(AppTransition appTransition,
- ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
- ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
- @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
-
- final ActivityRecord topOpeningApp = getTopApp(openingApps, false /* ignoreHidden */);
- final ActivityRecord topClosingApp = getTopApp(closingApps, true /* ignoreHidden */);
-
- // Determine if closing and opening app token sets are wallpaper targets, in which case
- // special animations are needed.
- final boolean openingAppHasWallpaper = canBeWallpaperTarget(openingApps)
- && wallpaperTarget != null;
- final boolean closingAppHasWallpaper = canBeWallpaperTarget(closingApps)
- && wallpaperTarget != null;
-
- // Keyguard transit has high priority.
- switch (appTransition.getKeyguardTransition()) {
- case TRANSIT_KEYGUARD_GOING_AWAY:
- return openingAppHasWallpaper ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
- : TRANSIT_OLD_KEYGUARD_GOING_AWAY;
- case TRANSIT_KEYGUARD_OCCLUDE:
- // When there is a closing app, the keyguard has already been occluded by an
- // activity, and another activity has started on top of that activity, so normal
- // app transition animation should be used.
- if (!closingApps.isEmpty()) {
- return TRANSIT_OLD_ACTIVITY_OPEN;
- }
- if (!openingApps.isEmpty() && openingApps.valueAt(0).getActivityType()
- == ACTIVITY_TYPE_DREAM) {
- return TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
- }
- return TRANSIT_OLD_KEYGUARD_OCCLUDE;
- case TRANSIT_KEYGUARD_UNOCCLUDE:
- return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
- }
-
- // Determine whether the top opening and closing activity is a dream activity. If so, this
- // has higher priority than others except keyguard transit.
- if (topOpeningApp != null && topOpeningApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
- return TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
- } else if (topClosingApp != null
- && topClosingApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
- return TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
- }
-
- // This is not keyguard transition and one of the app has request to skip app transition.
- if (skipAppTransitionAnimation) {
- return WindowManager.TRANSIT_OLD_UNSET;
- }
- @TransitionFlags final int flags = appTransition.getTransitFlags();
- @TransitionType final int firstTransit = appTransition.getFirstAppTransition();
-
- // Special transitions
- // TODO(new-app-transitions): Revisit if those can be rewritten by using flags.
- if (appTransition.containsTransitRequest(TRANSIT_CHANGE) && !changingContainers.isEmpty()) {
- @TransitContainerType int changingType =
- getTransitContainerType(changingContainers.valueAt(0));
- switch (changingType) {
- case TYPE_TASK:
- return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
- case TYPE_TASK_FRAGMENT:
- return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
- default:
- throw new IllegalStateException(
- "TRANSIT_CHANGE with unrecognized changing type=" + changingType);
- }
- }
- if ((flags & TRANSIT_FLAG_APP_CRASHED) != 0) {
- return TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
- }
- if (firstTransit == TRANSIT_NONE) {
- return TRANSIT_OLD_NONE;
- }
-
- /*
- * There are cases where we open/close a new task/activity, but in reality only a
- * translucent activity on top of existing activities is opening/closing. For that one, we
- * have a different animation because non of the task/activity animations actually work well
- * with translucent apps.
- */
- if (isNormalTransit(firstTransit)) {
- boolean allOpeningVisible = true;
- boolean allTranslucentOpeningApps = !openingApps.isEmpty();
- for (int i = openingApps.size() - 1; i >= 0; i--) {
- final ActivityRecord activity = openingApps.valueAt(i);
- if (!activity.isVisible()) {
- allOpeningVisible = false;
- if (activity.fillsParent()) {
- allTranslucentOpeningApps = false;
- }
- }
- }
- boolean allTranslucentClosingApps = !closingApps.isEmpty();
- for (int i = closingApps.size() - 1; i >= 0; i--) {
- if (closingApps.valueAt(i).fillsParent()) {
- allTranslucentClosingApps = false;
- break;
- }
- }
-
- if (allTranslucentClosingApps && allOpeningVisible) {
- return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
- }
- if (allTranslucentOpeningApps && closingApps.isEmpty()) {
- return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
- }
- }
-
- if (closingAppHasWallpaper && openingAppHasWallpaper) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Wallpaper animation!");
- switch (firstTransit) {
- case TRANSIT_OPEN:
- case TRANSIT_TO_FRONT:
- return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
- case TRANSIT_CLOSE:
- case TRANSIT_TO_BACK:
- return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
- }
- } else if (oldWallpaper != null && !openingApps.isEmpty()
- && !openingApps.contains(oldWallpaper.mActivityRecord)
- && closingApps.contains(oldWallpaper.mActivityRecord)
- && topClosingApp == oldWallpaper.mActivityRecord) {
- // We are transitioning from an activity with a wallpaper to one without.
- return TRANSIT_OLD_WALLPAPER_CLOSE;
- } else if (wallpaperTarget != null && wallpaperTarget.isVisible()
- && openingApps.contains(wallpaperTarget.mActivityRecord)
- && topOpeningApp == wallpaperTarget.mActivityRecord
- /* && transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE */) {
- // We are transitioning from an activity without
- // a wallpaper to now showing the wallpaper
- return TRANSIT_OLD_WALLPAPER_OPEN;
- }
-
- final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
- openingApps, closingApps, true /* visible */);
- final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
- openingApps, closingApps, false /* visible */);
- final WindowContainer<?> openingContainer = !openingWcs.isEmpty()
- ? openingWcs.valueAt(0) : null;
- final WindowContainer<?> closingContainer = !closingWcs.isEmpty()
- ? closingWcs.valueAt(0) : null;
- @TransitContainerType int openingType = getTransitContainerType(openingContainer);
- @TransitContainerType int closingType = getTransitContainerType(closingContainer);
- if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) {
- if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) {
- // If we are opening the home task, we want to play an animation as if
- // the task on top is being brought to back.
- return TRANSIT_OLD_TASK_TO_BACK;
- }
- return TRANSIT_OLD_TASK_TO_FRONT;
- }
- if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) {
- return TRANSIT_OLD_TASK_TO_BACK;
- }
- if (appTransition.containsTransitRequest(TRANSIT_OPEN)) {
- if (openingType == TYPE_TASK) {
- return (appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0
- ? TRANSIT_OLD_TASK_OPEN_BEHIND : TRANSIT_OLD_TASK_OPEN;
- }
- if (openingType == TYPE_ACTIVITY) {
- return TRANSIT_OLD_ACTIVITY_OPEN;
- }
- if (openingType == TYPE_TASK_FRAGMENT) {
- return TRANSIT_OLD_TASK_FRAGMENT_OPEN;
- }
- }
- if (appTransition.containsTransitRequest(TRANSIT_CLOSE)) {
- if (closingType == TYPE_TASK) {
- return TRANSIT_OLD_TASK_CLOSE;
- }
- if (closingType == TYPE_TASK_FRAGMENT) {
- return TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
- }
- if (closingType == TYPE_ACTIVITY) {
- for (int i = closingApps.size() - 1; i >= 0; i--) {
- if (closingApps.valueAt(i).visibleIgnoringKeyguard) {
- return TRANSIT_OLD_ACTIVITY_CLOSE;
- }
- }
- // Skip close activity transition since no closing app can be visible
- return WindowManager.TRANSIT_OLD_UNSET;
- }
- }
- if (appTransition.containsTransitRequest(TRANSIT_RELAUNCH)
- && !openingWcs.isEmpty() && !openingApps.isEmpty()) {
- return TRANSIT_OLD_ACTIVITY_RELAUNCH;
- }
- return TRANSIT_OLD_NONE;
- }
-
- @TransitContainerType
- private static int getTransitContainerType(@Nullable WindowContainer<?> container) {
- if (container == null) {
- return TYPE_NONE;
- }
- if (container.asTask() != null) {
- return TYPE_TASK;
- }
- if (container.asTaskFragment() != null) {
- return TYPE_TASK_FRAGMENT;
- }
- if (container.asActivityRecord() != null) {
- return TYPE_ACTIVITY;
- }
- return TYPE_NONE;
- }
-
- @Nullable
- private static WindowManager.LayoutParams getAnimLp(ActivityRecord activity) {
- final WindowState mainWindow = activity != null ? activity.findMainWindow() : null;
- return mainWindow != null ? mainWindow.mAttrs : null;
- }
-
- RemoteAnimationAdapter getRemoteAnimationOverride(@Nullable WindowContainer container,
- @TransitionOldType int transit, ArraySet<Integer> activityTypes) {
- if (container != null) {
- final RemoteAnimationDefinition definition = container.getRemoteAnimationDefinition();
- if (definition != null) {
- final RemoteAnimationAdapter adapter = definition.getAdapter(transit,
- activityTypes);
- if (adapter != null) {
- return adapter;
- }
- }
- }
- return mRemoteAnimationDefinition != null
- ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
- : null;
- }
-
- private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
- // We don't want to have the client to animate any non-app windows.
- // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
- // non-app windows will only be included with those transition types. And we don't currently
- // have any use case of those for TaskFragment transition.
- return shouldStartNonAppWindowAnimationsForKeyguardExit(transit)
- || shouldAttachNavBarToApp(mService, mDisplayContent, transit)
- || shouldStartWallpaperAnimation(mDisplayContent);
- }
-
- /**
- * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
- * parent {@link Task} before or after the transition.
- */
- private boolean transitionContainsTaskFragmentWithBoundsOverride() {
- for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
- final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
- if (wc.isEmbedded()) {
- // Contains embedded TaskFragment with bounds changed.
- return true;
- }
- }
- mTempTransitionWindows.clear();
- mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
- mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
- boolean containsTaskFragmentWithBoundsOverride = false;
- for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
- final TaskFragment tf = r.getTaskFragment();
- if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
- containsTaskFragmentWithBoundsOverride = true;
- break;
- }
- }
- mTempTransitionWindows.clear();
- return containsTaskFragmentWithBoundsOverride;
- }
-
- /**
- * Finds the common parent {@link Task} that is parent of all embedded app windows in the
- * current transition.
- * @return {@code null} if app windows in the transition are not children of the same Task, or
- * if none of the app windows is embedded.
- */
- @Nullable
- private Task findParentTaskForAllEmbeddedWindows() {
- mTempTransitionWindows.clear();
- mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
- mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
- mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
-
- // It should only animated by the organizer if all windows are below the same leaf Task.
- Task leafTask = null;
- for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
- if (r == null) {
- leafTask = null;
- break;
- }
- // There are also cases where the Task contains non-embedded activity, such as launching
- // split TaskFragments from a non-embedded activity.
- // The hierarchy may looks like this:
- // - Task
- // - Activity
- // - TaskFragment
- // - Activity
- // - TaskFragment
- // - Activity
- // We also want to have the organizer handle the transition for such case.
- final Task task = r.getTask();
- // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
- if (task == null || task.inPinnedWindowingMode()) {
- leafTask = null;
- break;
- }
- // We don't want the organizer to handle transition of other non-embedded Task.
- if (leafTask != null && leafTask != task) {
- leafTask = null;
- break;
- }
- final ActivityRecord rootActivity = task.getRootActivity();
- // We don't want the organizer to handle transition when the whole app is closing.
- if (rootActivity == null) {
- leafTask = null;
- break;
- }
- // We don't want the organizer to handle transition of non-embedded activity of other
- // app.
- if (r.getUid() != task.effectiveUid && !r.isEmbedded()) {
- leafTask = null;
- break;
- }
- leafTask = task;
- }
- mTempTransitionWindows.clear();
- return leafTask;
- }
-
- /**
- * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all embedded
- * {@link TaskFragment} belong to the given {@link Task}.
- * @return {@code null} if there is no such organizer, or if there are more than one.
- */
- @Nullable
- private ITaskFragmentOrganizer findTaskFragmentOrganizer(@Nullable Task task) {
- if (task == null) {
- return null;
- }
- // We don't support remote animation for Task with multiple TaskFragmentOrganizers.
- final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1];
- final boolean hasMultipleOrganizers = task.forAllLeafTaskFragments(taskFragment -> {
- final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer();
- if (tfOrganizer == null) {
- return false;
- }
- if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) {
- return true;
- }
- organizer[0] = tfOrganizer;
- return false;
- });
- if (hasMultipleOrganizers) {
- ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
- + " Task with multiple TaskFragmentOrganizers.");
- return null;
- }
- return organizer[0];
- }
-
- /**
- * Overrides the pending transition with the remote animation defined by the
- * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
- * {@link TaskFragment} that are organized by the same organizer.
- *
- * @return {@code true} if the transition is overridden.
- */
- private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes) {
- if (transitionMayContainNonAppWindows(transit)) {
- return false;
- }
- if (!transitionContainsTaskFragmentWithBoundsOverride()) {
- // No need to play TaskFragment remote animation if all embedded TaskFragment in the
- // transition fill the Task.
- return false;
- }
-
- final Task task = findParentTaskForAllEmbeddedWindows();
- final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
- final RemoteAnimationDefinition definition = organizer != null
- ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer)
- : null;
- final RemoteAnimationAdapter adapter = definition != null
- ? definition.getAdapter(transit, activityTypes)
- : null;
- if (adapter == null) {
- return false;
- }
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
- adapter, false /* sync */, true /*isActivityEmbedding*/);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Override with TaskFragment remote animation for transit=%s",
- AppTransition.appTransitionOldToString(transit));
-
- final int organizerUid = mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getTaskFragmentOrganizerUid(organizer);
- final boolean shouldDisableInputForRemoteAnimation = !task.isFullyTrustedEmbedding(
- organizerUid);
- final RemoteAnimationController remoteAnimationController =
- mDisplayContent.mAppTransition.getRemoteAnimationController();
- if (shouldDisableInputForRemoteAnimation && remoteAnimationController != null) {
- // We are going to use client-driven animation, Disable all input on activity windows
- // during the animation (unless it is fully trusted) to ensure it is safe to allow
- // client to animate the surfaces.
- // This is needed for all activity windows in the animation Task.
- remoteAnimationController.setOnRemoteAnimationReady(() -> {
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(true);
- task.forAllActivities(updateActivities);
- });
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment."
- + " Disabled all input during TaskFragment remote animation.", task.mTaskId);
- }
- return true;
- }
-
- /**
- * Overrides the pending transition with the remote animation defined for the transition in the
- * set of defined remote animations in the app window token.
- */
- private void overrideWithRemoteAnimationIfSet(@Nullable ActivityRecord animLpActivity,
- @TransitionOldType int transit, ArraySet<Integer> activityTypes) {
- RemoteAnimationAdapter adapter = null;
- if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
- // The crash transition has higher priority than any involved remote animations.
- } else if (AppTransition.isKeyguardGoingAwayTransitOld(transit)) {
- adapter = mRemoteAnimationDefinition != null
- ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
- : null;
- } else if (mDisplayContent.mAppTransition.getRemoteAnimationController() == null) {
- adapter = getRemoteAnimationOverride(animLpActivity, transit, activityTypes);
- }
- if (adapter != null) {
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
- }
- }
-
- @Nullable
- static Task findRootTaskFromContainer(WindowContainer wc) {
- return wc.asTaskFragment() != null ? wc.asTaskFragment().getRootTask()
- : wc.asActivityRecord().getRootTask();
- }
-
- @Nullable
- static ActivityRecord getAppFromContainer(WindowContainer wc) {
- return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
- : wc.asActivityRecord();
- }
-
- /**
- * @return The window token that determines the animation theme.
- */
- @Nullable
- private ActivityRecord findAnimLayoutParamsToken(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes, ArraySet<ActivityRecord> openingApps,
- ArraySet<ActivityRecord> closingApps, ArraySet<WindowContainer> changingApps) {
- ActivityRecord result;
-
- // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
- result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
- w -> w.getRemoteAnimationDefinition() != null
- && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
- if (result != null) {
- return result;
- }
- result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
- w -> w.fillsParent() && w.findMainWindow() != null);
- if (result != null) {
- return result;
- }
- return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
- w -> w.findMainWindow() != null);
- }
-
- /**
- * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
- * of apps in {@code array1}, {@code array2}, and {@code array3}.
- */
- private static ArraySet<Integer> collectActivityTypes(ArraySet<ActivityRecord> array1,
- ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3) {
- final ArraySet<Integer> result = new ArraySet<>();
- for (int i = array1.size() - 1; i >= 0; i--) {
- result.add(array1.valueAt(i).getActivityType());
- }
- for (int i = array2.size() - 1; i >= 0; i--) {
- result.add(array2.valueAt(i).getActivityType());
- }
- for (int i = array3.size() - 1; i >= 0; i--) {
- result.add(array3.valueAt(i).getActivityType());
- }
- return result;
- }
-
- private static ActivityRecord lookForHighestTokenWithFilter(ArraySet<ActivityRecord> array1,
- ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3,
- Predicate<ActivityRecord> filter) {
- final int array2base = array1.size();
- final int array3base = array2.size() + array2base;
- final int count = array3base + array3.size();
- int bestPrefixOrderIndex = Integer.MIN_VALUE;
- ActivityRecord bestToken = null;
- for (int i = 0; i < count; i++) {
- final WindowContainer wtoken = i < array2base
- ? array1.valueAt(i)
- : (i < array3base
- ? array2.valueAt(i - array2base)
- : array3.valueAt(i - array3base));
- final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
- final ActivityRecord r = getAppFromContainer(wtoken);
- if (r != null && filter.test(r) && prefixOrderIndex > bestPrefixOrderIndex) {
- bestPrefixOrderIndex = prefixOrderIndex;
- bestToken = r;
- }
- }
- return bestToken;
- }
-
- private boolean containsVoiceInteraction(ArraySet<ActivityRecord> apps) {
- for (int i = apps.size() - 1; i >= 0; i--) {
- if (apps.valueAt(i).mVoiceInteraction) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Apply animation to the set of window containers.
- *
- * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
- * @param apps The list of {@link ActivityRecord}s being transitioning.
- * @param transit The current transition type.
- * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
- * invisible.
- * @param animLp Layout parameters in which an app transition animation runs.
- * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
- * interaction session driving task.
- */
- private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
- @TransitionOldType int transit, boolean visible, LayoutParams animLp,
- boolean voiceInteraction) {
- final int wcsCount = wcs.size();
- for (int i = 0; i < wcsCount; i++) {
- final WindowContainer wc = wcs.valueAt(i);
- // If app transition animation target is promoted to higher level, SurfaceAnimator
- // triggers WC#onAnimationFinished only on the promoted target. So we need to take care
- // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
- // app transition.
- final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
- for (int j = 0; j < apps.size(); ++j) {
- final ActivityRecord app = apps.valueAt(j);
- if (app.isDescendantOf(wc)) {
- transitioningDescendants.add(app);
- }
- }
- wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
- }
- }
-
- /**
- * Returns {@code true} if a given {@link WindowContainer} is an embedded Task in
- * {@link TaskView}.
- *
- * Note that this is a short term workaround to support Android Auto until it migrate to
- * ShellTransition. This should only be used by {@link #getAnimationTargets}.
- *
- * TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled.
- */
- static boolean isTaskViewTask(WindowContainer wc) {
- // Use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and
- // it is not guaranteed to work this logic in the future version.
- boolean isTaskViewTask = wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer;
- if (isTaskViewTask) {
- return true;
- }
-
- WindowContainer parent = wc.getParent();
- boolean isParentATaskViewTask = parent != null
- && parent instanceof Task
- && ((Task) parent).mRemoveWithTaskOrganizer;
- return isParentATaskViewTask;
- }
-
- /**
- * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
- * animation targets to higher level in the window hierarchy if possible.
- *
- * @param visible {@code true} to get animation targets for opening apps, {@code false} to get
- * animation targets for closing apps.
- * @return {@link WindowContainer}s to be animated.
- */
- @VisibleForTesting
- static ArraySet<WindowContainer> getAnimationTargets(
- ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
- boolean visible) {
-
- // The candidates of animation targets, which might be able to promote to higher level.
- final ArrayDeque<WindowContainer> candidates = new ArrayDeque<>();
- final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
- for (int i = 0; i < apps.size(); ++i) {
- final ActivityRecord app = apps.valueAt(i);
- if (app.shouldApplyAnimation(visible)) {
- candidates.add(app);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Changing app %s visible=%b performLayout=%b",
- app, app.isVisible(), false);
- }
- }
-
- final ArraySet<ActivityRecord> otherApps = visible ? closingApps : openingApps;
- // Ancestors of closing apps while finding animation targets for opening apps, or ancestors
- // of opening apps while finding animation targets for closing apps.
- final ArraySet<WindowContainer> otherAncestors = new ArraySet<>();
- for (int i = 0; i < otherApps.size(); ++i) {
- for (WindowContainer wc = otherApps.valueAt(i); wc != null; wc = wc.getParent()) {
- otherAncestors.add(wc);
- }
- }
-
- // The final animation targets which cannot promote to higher level anymore.
- final ArraySet<WindowContainer> targets = new ArraySet<>();
- final ArrayList<WindowContainer> siblings = new ArrayList<>();
- while (!candidates.isEmpty()) {
- final WindowContainer current = candidates.removeFirst();
- final WindowContainer parent = current.getParent();
- siblings.clear();
- siblings.add(current);
- boolean canPromote = true;
-
- if (isTaskViewTask(current)) {
- // Don't animate an embedded Task in app transition. This is a short term workaround
- // to prevent conflict of surface hierarchy changes between legacy app transition
- // and TaskView (b/205189147).
- // TODO(b/213312721): Remove this once ShellTransition is enabled.
- continue;
- } else if (parent == null || !parent.canCreateRemoteAnimationTarget()
- // We cannot promote the animation on Task's parent when the task is in
- // clearing task in case the animating get stuck when performing the opening
- // task that behind it.
- || (current.asTask() != null && current.asTask().mInRemoveTask)
- // We cannot promote the animation to changing window. This may happen when an
- // activity is open in a TaskFragment that is resizing, while the existing
- // activity in the TaskFragment is reparented to another TaskFragment.
- || parent.isChangingAppTransition()) {
- canPromote = false;
- } else {
- // In case a descendant of the parent belongs to the other group, we cannot promote
- // the animation target from "current" to the parent.
- //
- // Example: Imagine we're checking if we can animate a Task instead of a set of
- // ActivityRecords. In case an activity starts a new activity within a same Task,
- // an ActivityRecord of an existing activity belongs to the opening apps, at the
- // same time, the other ActivityRecord of a new activity belongs to the closing
- // apps. In this case, we cannot promote the animation target to Task level, but
- // need to animate each individual activity.
- //
- // [Task] +- [ActivityRecord1] (in opening apps)
- // +- [ActivityRecord2] (in closing apps)
- if (otherAncestors.contains(parent)) {
- canPromote = false;
- }
-
- // If the current window container is a task with adjacent task set, the both
- // adjacent tasks will be opened or closed together. To get their opening or
- // closing animation target independently, skip promoting their animation targets.
- if (current.asTask() != null && current.asTask().hasAdjacentTask()) {
- canPromote = false;
- }
-
- // Find all siblings of the current WindowContainer in "candidates", move them into
- // a separate list "siblings", and checks if an animation target can be promoted
- // to its parent.
- //
- // We can promote an animation target to its parent if and only if all visible
- // siblings will be animating.
- //
- // Example: Imagine that a Task contains two visible activity record, but only one
- // of them is included in the opening apps and the other belongs to neither opening
- // or closing apps. This happens when an activity launches another translucent
- // activity in the same Task. In this case, we cannot animate Task, but have to
- // animate each activity, otherwise an activity behind the translucent activity also
- // animates.
- //
- // [Task] +- [ActivityRecord1] (visible, in opening apps)
- // +- [ActivityRecord2] (visible, not in opening apps)
- for (int j = 0; j < parent.getChildCount(); ++j) {
- final WindowContainer sibling = parent.getChildAt(j);
- if (candidates.remove(sibling)) {
- if (!isTaskViewTask(sibling)) {
- // Don't animate an embedded Task in app transition. This is a short
- // term workaround to prevent conflict of surface hierarchy changes
- // between legacy app transition and TaskView (b/205189147).
- // TODO(b/213312721): Remove this once ShellTransition is enabled.
- siblings.add(sibling);
- }
- } else if (sibling != current && sibling.isVisible()) {
- canPromote = false;
- }
- }
- }
-
- if (canPromote) {
- candidates.add(parent);
- } else {
- targets.addAll(siblings);
- }
- }
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
- apps, targets);
- return targets;
- }
-
- /**
- * Apply an app transition animation based on a set of {@link ActivityRecord}
- *
- * @param openingApps The list of opening apps to which an app transition animation applies.
- * @param closingApps The list of closing apps to which an app transition animation applies.
- * @param transit The current transition type.
- * @param animLp Layout parameters in which an app transition animation runs.
- * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
- * interaction session driving task.
- */
- private void applyAnimations(ArraySet<ActivityRecord> openingApps,
- ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
- LayoutParams animLp, boolean voiceInteraction) {
- if (transit == WindowManager.TRANSIT_OLD_UNSET
- || (openingApps.isEmpty() && closingApps.isEmpty())) {
- return;
- }
-
- if (AppTransition.isActivityTransitOld(transit)) {
- final ArrayList<Pair<ActivityRecord, Rect>> closingLetterboxes = new ArrayList();
- for (int i = 0; i < closingApps.size(); ++i) {
- ActivityRecord closingApp = closingApps.valueAt(i);
- if (closingApp.areBoundsLetterboxed()) {
- final Rect insets = closingApp.getLetterboxInsets();
- closingLetterboxes.add(new Pair(closingApp, insets));
- }
- }
-
- for (int i = 0; i < openingApps.size(); ++i) {
- ActivityRecord openingApp = openingApps.valueAt(i);
- if (openingApp.areBoundsLetterboxed()) {
- final Rect openingInsets = openingApp.getLetterboxInsets();
- for (Pair<ActivityRecord, Rect> closingLetterbox : closingLetterboxes) {
- final Rect closingInsets = closingLetterbox.second;
- if (openingInsets.equals(closingInsets)) {
- ActivityRecord closingApp = closingLetterbox.first;
- openingApp.setNeedsLetterboxedAnimation(true);
- closingApp.setNeedsLetterboxedAnimation(true);
- }
- }
- }
- }
- }
-
- final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
- openingApps, closingApps, true /* visible */);
- final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
- openingApps, closingApps, false /* visible */);
- applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
- voiceInteraction);
- applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
- voiceInteraction);
-
- for (int i = 0; i < openingApps.size(); ++i) {
- openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
- }
- for (int i = 0; i < closingApps.size(); ++i) {
- closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
- }
-
- final AccessibilityController accessibilityController =
- mDisplayContent.mWmService.mAccessibilityController;
- if (accessibilityController.hasCallbacks()) {
- accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
- }
- }
-
- private void handleOpeningApps() {
- final ArraySet<ActivityRecord> openingApps = mDisplayContent.mOpeningApps;
- final int appsCount = openingApps.size();
-
- for (int i = 0; i < appsCount; i++) {
- final ActivityRecord app = openingApps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now opening app %s", app);
-
- app.commitVisibility(true /* visible */, false /* performLayout */);
-
- // In case a trampoline activity is used, it can happen that a new ActivityRecord is
- // added and a new app transition starts before the previous app transition animation
- // ends. So we cannot simply use app.isAnimating(PARENTS) to determine if the app must
- // to be added to the list of tokens to be notified of app transition complete.
- final WindowContainer wc = app.getAnimatingContainer(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION);
- if (wc == null || !wc.getAnimationSources().contains(app)) {
- // This token isn't going to be animating. Add it to the list of tokens to
- // be notified of app transition complete since the notification will not be
- // sent be the app window animator.
- mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(app.token);
- }
- app.updateReportedVisibilityLocked();
- app.showAllWindowsLocked();
-
- if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
- app.attachThumbnailAnimation();
- } else if (mDisplayContent.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
- app.attachCrossProfileAppsThumbnailAnimation();
- }
- }
- }
-
- private void handleClosingApps() {
- final ArraySet<ActivityRecord> closingApps = mDisplayContent.mClosingApps;
- final int appsCount = closingApps.size();
-
- for (int i = 0; i < appsCount; i++) {
- final ActivityRecord app = closingApps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now closing app %s", app);
-
- app.commitVisibility(false /* visible */, false /* performLayout */);
- app.updateReportedVisibilityLocked();
- // Force the allDrawn flag, because we want to start
- // this guy's animations regardless of whether it's
- // gotten drawn.
- app.allDrawn = true;
- // Ensure that apps that are mid-starting are also scheduled to have their
- // starting windows removed after the animation is complete
- if (app.mStartingWindow != null && !app.mStartingWindow.mAnimatingExit) {
- app.removeStartingWindow();
- }
-
- if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailDown()) {
- app.attachThumbnailAnimation();
- }
- }
- }
-
- private void handleClosingChangingContainers() {
- final ArrayMap<WindowContainer, Rect> containers =
- mDisplayContent.mClosingChangingContainers;
- while (!containers.isEmpty()) {
- final WindowContainer container = containers.keyAt(0);
- containers.remove(container);
-
- // For closing changing windows that are part of the transition, they should have been
- // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter()
- // If the closing changing TaskFragment is not part of the transition, update its
- // surface after removing it from mClosingChangingContainers.
- final TaskFragment taskFragment = container.asTaskFragment();
- if (taskFragment != null) {
- taskFragment.updateOrganizedTaskFragmentSurface();
- }
- }
- }
-
- private void handleChangingApps(@TransitionOldType int transit) {
- final ArraySet<WindowContainer> apps = mDisplayContent.mChangingContainers;
- final int appsCount = apps.size();
- for (int i = 0; i < appsCount; i++) {
- WindowContainer wc = apps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", wc);
- wc.applyAnimation(null, transit, true, false, null /* sources */);
- }
- }
-
- private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
- ArrayMap<WindowContainer, Integer> outReasons) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Checking %d opening apps (timeout=%b)...", apps.size(),
- mDisplayContent.mAppTransition.isTimeout());
- if (mDisplayContent.mAppTransition.isTimeout()) {
- return true;
- }
-
- for (int i = 0; i < apps.size(); i++) {
- WindowContainer wc = apps.valueAt(i);
- final ActivityRecord activity = getAppFromContainer(wc);
- if (activity == null) {
- continue;
- }
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
- + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
- activity.startingMoved, activity.isRelaunching(),
- activity.mStartingWindow);
- final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
- return false;
- }
- if (allDrawn) {
- outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN);
- } else {
- outReasons.put(activity,
- activity.mStartingData instanceof SplashScreenStartingData
- ? APP_TRANSITION_SPLASH_SCREEN
- : APP_TRANSITION_SNAPSHOT);
- }
- }
-
- // We also need to wait for the specs to be fetched, if needed.
- if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true");
- return false;
- }
-
- if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s",
- mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
- return false;
- }
-
- // If the wallpaper is visible, we need to check it's ready too.
- return !mWallpaperControllerLocked.isWallpaperVisible()
- || mWallpaperControllerLocked.wallpaperTransitionReady();
- }
-
- private boolean transitionGoodToGoForTaskFragments() {
- if (mDisplayContent.mAppTransition.isTimeout()) {
- return true;
- }
-
- // Check all Tasks in this transition. This is needed because new TaskFragment created for
- // launching activity may not be in the tracking lists, but we still want to wait for the
- // activity launch to start the transition.
- final ArraySet<Task> rootTasks = new ArraySet<>();
- for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) {
- rootTasks.add(mDisplayContent.mOpeningApps.valueAt(i).getRootTask());
- }
- for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) {
- rootTasks.add(mDisplayContent.mClosingApps.valueAt(i).getRootTask());
- }
- for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
- rootTasks.add(
- findRootTaskFromContainer(mDisplayContent.mChangingContainers.valueAt(i)));
- }
-
- // Organized TaskFragment can be empty for two situations:
- // 1. New created and is waiting for Activity launch. In this case, we want to wait for
- // the Activity launch to trigger the transition.
- // 2. Last Activity is just removed. In this case, we want to wait for organizer to
- // remove the TaskFragment because it may also want to change other TaskFragments in
- // the same transition.
- for (int i = rootTasks.size() - 1; i >= 0; i--) {
- final Task rootTask = rootTasks.valueAt(i);
- if (rootTask == null) {
- // It is possible that one activity may have been removed from the hierarchy. No
- // need to check for this case.
- continue;
- }
- final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> {
- if (!taskFragment.isReadyToTransit()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s",
- taskFragment);
- return true;
- }
- return false;
- });
- if (notReady) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Identifies whether the current transition occurs within a single task or not. This is used
- * to determine whether animations should be clipped to the task bounds instead of root task
- * bounds.
- */
- @VisibleForTesting
- boolean isTransitWithinTask(@TransitionOldType int transit, Task task) {
- if (task == null
- || !mDisplayContent.mChangingContainers.isEmpty()) {
- // if there is no task, then we can't constrain to the task.
- // if anything is changing, it can animate outside its task.
- return false;
- }
- if (!(transit == TRANSIT_OLD_ACTIVITY_OPEN
- || transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_ACTIVITY_RELAUNCH)) {
- // only activity-level transitions will be within-task.
- return false;
- }
- // check that all components are in the task.
- for (ActivityRecord activity : mDisplayContent.mOpeningApps) {
- Task activityTask = activity.getTask();
- if (activityTask != task) {
- return false;
- }
- }
- for (ActivityRecord activity : mDisplayContent.mClosingApps) {
- if (activity.getTask() != task) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean canBeWallpaperTarget(ArraySet<ActivityRecord> apps) {
- for (int i = apps.size() - 1; i >= 0; i--) {
- if (apps.valueAt(i).windowsCanBeWallpaperTarget()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Finds the top app in a list of apps, using its {@link ActivityRecord#getPrefixOrderIndex} to
- * compare z-order.
- *
- * @param apps The list of apps to search.
- * @param ignoreInvisible If set to true, ignores apps that are not
- * {@link ActivityRecord#isVisible}.
- * @return The top {@link ActivityRecord}.
- */
- private static ActivityRecord getTopApp(ArraySet<? extends WindowContainer> apps,
- boolean ignoreInvisible) {
- int topPrefixOrderIndex = Integer.MIN_VALUE;
- ActivityRecord topApp = null;
- for (int i = apps.size() - 1; i >= 0; i--) {
- final ActivityRecord app = getAppFromContainer(apps.valueAt(i));
- if (app == null || ignoreInvisible && !app.isVisible()) {
- continue;
- }
- final int prefixOrderIndex = app.getPrefixOrderIndex();
- if (prefixOrderIndex > topPrefixOrderIndex) {
- topPrefixOrderIndex = prefixOrderIndex;
- topApp = app;
- }
- }
- return topApp;
- }
-}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 79bed3d8453d..e76a83453a9d 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -388,8 +388,7 @@ class BackNavigationController {
removedWindowContainer);
mBackAnimationInProgress = builder != null;
if (mBackAnimationInProgress) {
- if (removedWindowContainer.mTransitionController.inTransition()
- || mWindowManagerService.mSyncEngine.hasPendingSyncSets()) {
+ if (removedWindowContainer.mTransitionController.inTransition()) {
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
"Pending back animation due to another animation is running");
mPendingAnimationBuilder = builder;
@@ -817,6 +816,8 @@ class BackNavigationController {
if (openingTransition && !visible && mAnimationHandler.isTarget(ar, false /* open */)
&& ar.mTransitionController.isCollecting(ar)) {
final TransitionController controller = ar.mTransitionController;
+ final Transition transition = controller.getCollectingTransition();
+ final int switchType = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType;
boolean collectTask = false;
ActivityRecord changedActivity = null;
for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
@@ -829,8 +830,16 @@ class BackNavigationController {
changedActivity = next;
}
}
- if (collectTask && mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType
- == AnimationHandler.TASK_SWITCH) {
+ if (Flags.unifyBackNavigationTransition()) {
+ for (int i = mAnimationHandler.mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+ collectAnimatableTarget(transition, switchType,
+ mAnimationHandler.mOpenAnimAdaptor.mAdaptors[i].mTarget,
+ false /* isTop */);
+ }
+ collectAnimatableTarget(transition, switchType,
+ mAnimationHandler.mCloseAdaptor.mTarget, true /* isTop */);
+ }
+ if (collectTask && switchType == AnimationHandler.TASK_SWITCH) {
final Task topTask = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].getTopTask();
if (topTask != null) {
WindowContainer parent = mAnimationHandler.mOpenActivities[0].getParent();
@@ -848,6 +857,18 @@ class BackNavigationController {
}
}
+ private static void collectAnimatableTarget(Transition transition, int switchType,
+ WindowContainer animatingTarget, boolean isTop) {
+ if ((switchType == AnimationHandler.ACTIVITY_SWITCH
+ && (animatingTarget.asActivityRecord() != null
+ || animatingTarget.asTaskFragment() != null))
+ || (switchType == AnimationHandler.TASK_SWITCH
+ && animatingTarget.asTask() != null)) {
+ transition.collect(animatingTarget);
+ transition.setBackGestureAnimation(animatingTarget, isTop);
+ }
+ }
+
// For shell transition
/**
* Check whether the transition targets was animated by back gesture animation.
@@ -992,8 +1013,8 @@ class BackNavigationController {
return;
}
- if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
- Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+ if (mWindowManagerService.mRoot.mTransitionController.inTransition()) {
+ Slog.v(TAG, "Skip predictive back transition, another transition is playing");
cancelPendingAnimation();
return;
}
@@ -1098,7 +1119,7 @@ class BackNavigationController {
}
final Transition prepareTransition = builder.prepareTransitionIfNeeded(
- openingActivities);
+ openingActivities, close, open);
final SurfaceControl.Transaction st = openingActivities[0].getSyncTransaction();
final SurfaceControl.Transaction ct = prepareTransition != null
? st : close.getPendingTransaction();
@@ -1790,7 +1811,8 @@ class BackNavigationController {
return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
}
- private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities) {
+ private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities,
+ WindowContainer promoteToClose, WindowContainer[] promoteToOpen) {
if (Flags.unifyBackNavigationTransition()) {
if (mCloseTarget.asWindowState() != null) {
return null;
@@ -1806,11 +1828,11 @@ class BackNavigationController {
final TransitionController tc = visibleOpenActivities[0].mTransitionController;
final Transition prepareOpen = tc.createTransition(
TRANSIT_PREPARE_BACK_NAVIGATION);
- tc.collect(mCloseTarget);
- prepareOpen.setBackGestureAnimation(mCloseTarget, true /* isTop */);
- for (int i = mOpenTargets.length - 1; i >= 0; --i) {
- tc.collect(mOpenTargets[i]);
- prepareOpen.setBackGestureAnimation(mOpenTargets[i], false /* isTop */);
+ tc.collect(promoteToClose);
+ prepareOpen.setBackGestureAnimation(promoteToClose, true /* isTop */);
+ for (int i = promoteToOpen.length - 1; i >= 0; --i) {
+ tc.collect(promoteToOpen[i]);
+ prepareOpen.setBackGestureAnimation(promoteToOpen[i], false /* isTop */);
}
if (!makeVisibles.isEmpty()) {
setLaunchBehind(visibleOpenActivities);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6fa92176959e..682f3d8cf1e5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -226,7 +226,6 @@ import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.PrivacyIndicatorBounds;
-import android.view.RemoteAnimationDefinition;
import android.view.RoundedCorners;
import android.view.Surface;
import android.view.Surface.Rotation;
@@ -367,8 +366,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private int mMaxUiWidth = 0;
final AppTransition mAppTransition;
- final AppTransitionController mAppTransitionController;
- boolean mSkipAppTransitionAnimation = false;
final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
@@ -547,9 +544,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// TODO(multi-display): remove some of the usages.
boolean isDefaultDisplay;
- /** Indicates whether any presentation is shown on this display. */
- boolean mIsPresenting;
-
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Region mTmpRegion = new Region();
@@ -1164,7 +1158,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
- mAppTransitionController = new AppTransitionController(mWmService, this);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -1556,10 +1549,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mInputMethodSurfaceParentWindow;
}
- void registerRemoteAnimations(RemoteAnimationDefinition definition) {
- mAppTransitionController.registerRemoteAnimations(definition);
- }
-
void reconfigureDisplayLocked() {
if (!isReady()) {
return;
@@ -5607,20 +5596,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * Transfer app transition from other display to this display.
- *
- * @param from Display from where the app transition is transferred.
- *
- * TODO(new-app-transition): Remove this once the shell handles app transition.
- */
- void transferAppTransitionFrom(DisplayContent from) {
- final boolean prepared = mAppTransition.transferFrom(from.mAppTransition);
- if (prepared && okToAnimate()) {
- mSkipAppTransitionAnimation = false;
- }
- }
-
- /**
* @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
*/
@Deprecated
@@ -5634,10 +5609,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Deprecated
void prepareAppTransition(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
- final boolean prepared = mAppTransition.prepareAppTransition(transit, flags);
- if (prepared && okToAnimate() && transit != TRANSIT_NONE) {
- mSkipAppTransitionAnimation = false;
- }
+ mAppTransition.prepareAppTransition(transit, flags);
}
/**
@@ -7112,14 +7084,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
+ * @return an integer as the changed requested visible insets types.
* @see #getRequestedVisibleTypes()
*/
- void updateRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
- int newRequestedVisibleTypes =
+ @InsetsType int updateRequestedVisibleTypes(
+ @InsetsType int visibleTypes, @InsetsType int mask) {
+ final int newRequestedVisibleTypes =
(mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
if (mRequestedVisibleTypes != newRequestedVisibleTypes) {
+ final int changedTypes = mRequestedVisibleTypes ^ newRequestedVisibleTypes;
mRequestedVisibleTypes = newRequestedVisibleTypes;
+ return changedTypes;
}
+ return 0;
}
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 907d0dc2e183..7b6fc9e5694d 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -34,6 +34,7 @@ import android.util.proto.ProtoOutputStream;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.InputTransferToken;
import com.android.internal.protolog.ProtoLog;
@@ -260,7 +261,7 @@ class EmbeddedWindowController {
// The EmbeddedWindow can only request the IME. All other insets types are requested by
// the host window.
- private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0;
+ private @InsetsType int mRequestedVisibleTypes = 0;
/** Whether the gesture is transferred to embedded window. */
boolean mGestureToEmbedded = false;
@@ -354,24 +355,28 @@ class EmbeddedWindowController {
}
@Override
- public boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types) {
+ public boolean isRequestedVisible(@InsetsType int types) {
return (mRequestedVisibleTypes & types) != 0;
}
@Override
- public @WindowInsets.Type.InsetsType int getRequestedVisibleTypes() {
+ public @InsetsType int getRequestedVisibleTypes() {
return mRequestedVisibleTypes;
}
/**
* Only the IME can be requested from the EmbeddedWindow.
- * @param requestedVisibleTypes other types than {@link WindowInsets.Type.IME} are
+ * @param requestedVisibleTypes other types than {@link WindowInsets.Type#ime()} are
* not sent to system server via WindowlessWindowManager.
+ * @return an integer as the changed requested visible insets types.
*/
- void setRequestedVisibleTypes(@WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+ @InsetsType int setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ final int changedTypes = mRequestedVisibleTypes ^ requestedVisibleTypes;
mRequestedVisibleTypes = requestedVisibleTypes;
+ return changedTypes;
}
+ return 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index ef1f58eafacc..b4d55a160631 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -437,9 +437,9 @@ class InsetsPolicy {
return originalState;
}
- void onRequestedVisibleTypesChanged(InsetsTarget caller,
+ void onRequestedVisibleTypesChanged(InsetsTarget caller, @InsetsType int changedTypes,
@Nullable ImeTracker.Token statsToken) {
- mStateController.onRequestedVisibleTypesChanged(caller, statsToken);
+ mStateController.onRequestedVisibleTypesChanged(caller, changedTypes, statsToken);
checkAbortTransient(caller);
updateBarControlTarget(mFocusedWin);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9202cf2d5792..164abab992d8 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -219,14 +219,20 @@ class InsetsStateController {
}
}
- void onRequestedVisibleTypesChanged(InsetsTarget caller,
+ void onRequestedVisibleTypesChanged(InsetsTarget caller, @InsetsType int changedTypes,
@Nullable ImeTracker.Token statsToken) {
boolean changed = false;
for (int i = mProviders.size() - 1; i >= 0; i--) {
final InsetsSourceProvider provider = mProviders.valueAt(i);
- final boolean isImeProvider = provider.getSource().getType() == WindowInsets.Type.ime();
- changed |= provider.updateClientVisibility(caller,
- isImeProvider ? statsToken : null);
+ final @InsetsType int type = provider.getSource().getType();
+ if ((type & changedTypes) != 0) {
+ final boolean isImeProvider = type == WindowInsets.Type.ime();
+ changed |= provider.updateClientVisibility(
+ caller, isImeProvider ? statsToken : null)
+ // Fake control target cannot change the client visibility, but it should
+ // change the insets with its newly requested visibility.
+ || (caller == provider.getFakeControlTarget());
+ }
}
if (changed) {
notifyInsetsChanged();
@@ -435,7 +441,8 @@ class InsetsStateController {
for (int i = newControlTargets.size() - 1; i >= 0; i--) {
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
// IME provider. Check if we have to create a new request here
- onRequestedVisibleTypesChanged(newControlTargets.valueAt(i), null /* statsToken */);
+ onRequestedVisibleTypesChanged(newControlTargets.valueAt(i),
+ WindowInsets.Type.all(), null /* statsToken */);
}
newControlTargets.clear();
if (!android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
new file mode 100644
index 000000000000..69463433827f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
+
+import android.annotation.NonNull;
+import android.util.IntArray;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.WmProtoLogGroups;
+
+/**
+ * Manages presentation windows.
+ */
+class PresentationController {
+
+ // TODO(b/395475549): Add support for display add/remove, and activity move across displays.
+ private final IntArray mPresentingDisplayIds = new IntArray();
+
+ PresentationController() {}
+
+ private boolean isPresenting(int displayId) {
+ return mPresentingDisplayIds.contains(displayId);
+ }
+
+ boolean shouldOccludeActivities(int displayId) {
+ // All activities on the presenting display must be hidden so that malicious apps can't do
+ // tap jacking (b/391466268).
+ // For now, this should only be applied to external displays because presentations can only
+ // be shown on them.
+ // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
+ // the presentation won't stop its controlling activity.
+ return enablePresentationForConnectedDisplays() && isPresenting(displayId);
+ }
+
+ void onPresentationAdded(@NonNull WindowState win) {
+ final int displayId = win.getDisplayId();
+ if (isPresenting(displayId)) {
+ return;
+ }
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
+ win.getDisplayId(), win);
+ mPresentingDisplayIds.add(win.getDisplayId());
+ if (enablePresentationForConnectedDisplays()) {
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ }
+ win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
+ }
+
+ void onPresentationRemoved(@NonNull WindowState win) {
+ final int displayId = win.getDisplayId();
+ if (!isPresenting(displayId)) {
+ return;
+ }
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION,
+ "Presentation removed from display %d: %s", win.getDisplayId(), win);
+ // TODO(b/393945496): Make sure that there's one presentation at most per display.
+ final int displayIdIndex = mPresentingDisplayIds.indexOf(displayId);
+ if (displayIdIndex != -1) {
+ mPresentingDisplayIds.remove(displayIdIndex);
+ }
+ if (enablePresentationForConnectedDisplays()) {
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ }
+ win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 95d9b3e612ac..c93efd327096 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -35,7 +35,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_WAKE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
@@ -68,7 +67,6 @@ import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
-import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -803,8 +801,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
mWmService.mSyncEngine.onSurfacePlacement();
- checkAppTransitionReady(surfacePlacer);
-
mWmService.mAtmService.mBackNavigationController
.checkAnimationReady(defaultDisplay.mWallpaperController);
@@ -898,38 +894,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (DEBUG_WINDOW_TRACE) Slog.e(TAG, "performSurfacePlacementInner exit");
}
- private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
- // Trace all displays app transition by Z-order for pending layout change.
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent curDisplay = mChildren.get(i);
-
- // If we are ready to perform an app transition, check through all of the app tokens
- // to be shown and see if they are ready to go.
- if (curDisplay.mAppTransition.isReady()) {
- // handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
- curDisplay.mAppTransitionController.handleAppTransitionReady();
- if (DEBUG_LAYOUT_REPEATS) {
- surfacePlacer.debugLayoutRepeats("after handleAppTransitionReady",
- curDisplay.pendingLayoutChanges);
- }
- }
-
- if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) {
- // We have finished the animation of an app transition. To do this, we have
- // delayed a lot of operations like showing and hiding apps, moving apps in
- // Z-order, etc.
- // The app token list reflects the correct Z-order, but the window list may now
- // be out of sync with it. So here we will just rebuild the entire app window
- // list. Fun!
- curDisplay.handleAnimatingStoppedAndTransition();
- if (DEBUG_LAYOUT_REPEATS) {
- surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
- curDisplay.pendingLayoutChanges);
- }
- }
- }
- }
-
private void applySurfaceChangesTransaction() {
// TODO(multi-display): Support these features on secondary screens.
final DisplayContent defaultDc = mDefaultDisplay;
@@ -2266,20 +2230,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Ensure the leash of new task is in sync with its current bounds after reparent.
rootTask.maybeApplyLastRecentsAnimationTransaction();
-
- // In the case of this activity entering PIP due to it being moved to the back,
- // the old activity would have a TRANSIT_TASK_TO_BACK transition that needs to be
- // ran. But, since its visibility did not change (note how it was STOPPED/not
- // visible, and with it now at the back stack, it remains not visible), the logic to
- // add the transition is automatically skipped. We then add this activity manually
- // to the list of apps being closed, and request its transition to be ran.
- final ActivityRecord oldTopActivity = task.getTopMostActivity();
- if (oldTopActivity != null && oldTopActivity.isState(STOPPED)
- && task.getDisplayContent().mAppTransition.containsTransitRequest(
- TRANSIT_TO_BACK)) {
- task.getDisplayContent().mClosingApps.add(oldTopActivity);
- oldTopActivity.mRequestForceTransition = true;
- }
}
// TODO(remove-legacy-transit): Move this to the `singleActivity` case when removing
@@ -2958,20 +2908,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
display.mAllSleepTokens.remove(token);
if (display.mAllSleepTokens.isEmpty()) {
mService.updateSleepIfNeededLocked();
- // Assuming no lock screen is set and a user launches an activity, turns off the screen
- // and turn on the screen again, then the launched activity should be displayed on the
- // screen without app transition animation. When the screen turns on, both keyguard
- // sleep token and display off sleep token are removed, but the order is
- // non-deterministic.
- // Note: Display#mSkipAppTransitionAnimation will be ignored when keyguard related
- // transition exists, so this affects only when no lock screen is set. Otherwise
- // keyguard going away animation will be played.
- // See also AppTransitionController#getTransitCompatType for more details.
- if ((!mTaskSupervisor.getKeyguardController().isKeyguardOccluded(display.mDisplayId)
- && token.mTag.equals(KEYGUARD_SLEEP_TOKEN_TAG))
- || token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) {
- display.mSkipAppTransitionAnimation = true;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1ad5988e3c2e..8d198b26f396 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -704,9 +704,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
}
- win.setRequestedVisibleTypes(requestedVisibleTypes);
+ final @InsetsType int changedTypes =
+ win.setRequestedVisibleTypes(requestedVisibleTypes);
win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
- imeStatsToken);
+ changedTypes, imeStatsToken);
final Task task = win.getTask();
if (task != null) {
task.dispatchTaskInfoChangedIfNeeded(/* forced= */ true);
@@ -723,10 +724,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
// TODO(b/353463205) Use different phase here
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
- embeddedWindow.setRequestedVisibleTypes(
+ final @InsetsType int changedTypes = embeddedWindow.setRequestedVisibleTypes(
requestedVisibleTypes & WindowInsets.Type.ime());
embeddedWindow.getDisplayContent().getInsetsPolicy()
- .onRequestedVisibleTypesChanged(embeddedWindow, imeStatsToken);
+ .onRequestedVisibleTypesChanged(
+ embeddedWindow, changedTypes, imeStatsToken);
} else {
ImeTracker.forLogging().onFailed(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index dcdffa416e33..2664dcd3ae3f 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -72,11 +72,6 @@ class SnapshotController {
mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
}
- // For legacy transition, which won't support activity snapshot
- void onTransitionStarting(DisplayContent displayContent) {
- mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
- }
-
// For shell transition, record snapshots before transaction start.
void onTransactionReady(@WindowManager.TransitionType int type,
ArrayList<Transition.ChangeInfo> changeInfos) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3dfff39e9b68..c5425fedf2ac 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -132,10 +132,7 @@ public class SurfaceAnimator {
animationFinishCallback.onAnimationFinished(type, anim);
}
};
- // If both the Animatable and AnimationAdapter requests to be deferred, only the
- // first one will be called.
- if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)
- || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
+ if (!anim.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
resetAndInvokeFinish.run();
}
mAnimationFinished = true;
@@ -639,23 +636,5 @@ public class SurfaceAnimator {
* @return The height of the surface to be animated.
*/
int getSurfaceHeight();
-
- /**
- * Gets called when the animation is about to finish and gives the client the opportunity to
- * defer finishing the animation, i.e. it keeps the leash around until the client calls
- * {@link #cancelAnimation}.
- * <p>
- * {@link AnimationAdapter} has a similar method which is called only if this method returns
- * false. This mean that if both this {@link Animatable} and the {@link AnimationAdapter}
- * request to be deferred, this method is the sole responsible to call
- * endDeferFinishCallback. On the other hand, the animation finish might still be deferred
- * if this method return false and the one from the {@link AnimationAdapter} returns true.
- *
- * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
- * @return Whether the client would like to defer the animation finish.
- */
- default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- return false;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f75e7175b4d2..3abab8bf62c2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -508,9 +508,6 @@ class Task extends TaskFragment {
*/
boolean mAllowForceResizeOverride = true;
- private final AnimatingActivityRegistry mAnimatingActivityRegistry =
- new AnimatingActivityRegistry();
-
private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_TASK_MSG + 1;
private final Handler mHandler;
@@ -1122,17 +1119,6 @@ class Task extends TaskFragment {
// already ran fully within super.onParentChanged
updateTaskOrganizerState();
- // TODO(b/168037178): The check for null display content and setting it to null doesn't
- // really make sense here...
-
- // TODO(b/168037178): This is mostly taking care of the case where the stask is removing
- // from the display, so we should probably consolidate it there instead.
-
- if (getParent() == null && mDisplayContent != null) {
- mDisplayContent = null;
- mWmService.mWindowPlacerLocked.requestTraversal();
- }
-
if (oldParent != null) {
final Task oldParentTask = oldParent.asTask();
if (oldParentTask != null) {
@@ -1185,9 +1171,6 @@ class Task extends TaskFragment {
}
mRootWindowContainer.updateUIDsPresentOnDisplay();
-
- // Ensure all animations are finished at same time in split-screen mode.
- forAllActivities(ActivityRecord::updateAnimatingActivityRegistry);
}
@Override
@@ -2770,6 +2753,7 @@ class Task extends TaskFragment {
}
super.removeImmediately();
+ mDisplayContent = null;
mRemoving = false;
}
@@ -3345,13 +3329,6 @@ class Task extends TaskFragment {
mLastSurfaceShowing = show;
}
- @Override
- void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- super.dump(pw, prefix, dumpAll);
- mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
- }
-
-
/**
* Fills in a {@link TaskInfo} with information from this task. Note that the base intent in the
* task info will not include any extras or clip data.
@@ -6313,10 +6290,6 @@ class Task extends TaskFragment {
return mDisplayContent.getDisplayInfo();
}
- AnimatingActivityRegistry getAnimatingActivityRegistry() {
- return mAnimatingActivityRegistry;
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 324852d1a410..97a1a34336e9 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -394,6 +394,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
*/
private boolean mAllowTransitionWhenEmpty;
+ /**
+ * Specifies which configuration changes should trigger TaskFragment info changed callbacks.
+ * Only system TaskFragment organizers are allowed to set this value.
+ */
+ private @ActivityInfo.Config int mConfigurationChangeMaskForOrganizer;
+
/** When set, will force the task to report as invisible. */
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -656,6 +662,17 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
}
+ void setConfigurationChangeMaskForOrganizer(@ActivityInfo.Config int mask) {
+ // Only system organizers are allowed to set configuration change mask.
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(mTaskFragmentOrganizer.asBinder())) {
+ mConfigurationChangeMaskForOrganizer = mask;
+ }
+ }
+
+ @ActivityInfo.Config int getConfigurationChangeMaskForOrganizer() {
+ return mConfigurationChangeMaskForOrganizer;
+ }
+
/** @see #mIsolatedNav */
boolean isIsolatedNav() {
return isEmbedded() && mIsolatedNav;
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index e63107cdc720..ae329d787156 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -349,8 +349,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
// Check if the info is different from the last reported info.
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
- if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
- info.getConfiguration(), lastInfo.getConfiguration())) {
+ final int configurationChangeMask = tf.getConfigurationChangeMaskForOrganizer();
+ if (info.equalsForTaskFragmentOrganizer(lastInfo)
+ && configurationsAreEqualForOrganizer(info.getConfiguration(),
+ lastInfo.getConfiguration(), configurationChangeMask)) {
return null;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 432ed1d0b61d..8a937721b347 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -113,27 +113,6 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
}
- // Still needed for legacy transition.(AppTransitionControllerTest)
- void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
- if (shouldDisableSnapshots()) {
- return;
- }
- // We need to take a snapshot of the task if and only if all activities of the task are
- // either closing or hidden.
- mTmpTasks.clear();
- for (int i = closingApps.size() - 1; i >= 0; i--) {
- final ActivityRecord activity = closingApps.valueAt(i);
- if (activity.isActivityTypeHome()) continue;
- final Task task = activity.getTask();
- if (task == null) continue;
-
- getClosingTasksInner(task, mTmpTasks);
- }
- snapshotTasks(mTmpTasks);
- mTmpTasks.clear();
- mSkipClosingAppSnapshotTasks.clear();
- }
-
/**
* Adds the given {@param tasks} to the list of tasks which should not have their snapshots
* taken upon the next processing of the set of closing apps. The caller is responsible for
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 55c2668f62d0..7af542f10127 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3359,7 +3359,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
- if (AppTransitionController.isTaskViewTask(this) || (isOrganized()
+ if ((isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
&& getWindowingMode() != WINDOWING_MODE_FULLSCREEN
&& getWindowingMode() != WINDOWING_MODE_FREEFORM
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d5626661725e..d699a689459e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,7 +157,6 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -348,7 +347,6 @@ import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
@@ -450,11 +448,6 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Use WMShell for app transition.
*/
- private static final String ENABLE_SHELL_TRANSITIONS = "persist.wm.debug.shell_transit";
-
- /**
- * @see #ENABLE_SHELL_TRANSITIONS
- */
public static final boolean sEnableShellTransitions = getShellTransitEnabled();
/**
@@ -503,6 +496,8 @@ public class WindowManagerService extends IWindowManager.Stub
final StartingSurfaceController mStartingSurfaceController;
+ final PresentationController mPresentationController;
+
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
@@ -1433,6 +1428,7 @@ public class WindowManagerService extends IWindowManager.Stub
setGlobalShadowSettings();
mAnrController = new AnrController(this);
mStartingSurfaceController = new StartingSurfaceController(this);
+ mPresentationController = new PresentationController();
mBlurController = new BlurController(mContext, mPowerManager);
mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
@@ -1937,16 +1933,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
outSizeCompatScale[0] = win.getCompatScaleForClient();
- if (res >= ADD_OKAY
- && (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION)) {
- displayContent.mIsPresenting = true;
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- displayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
- mDisplayManagerInternal.onPresentation(displayContent.getDisplay().getDisplayId(),
- /*isShown=*/ true);
+ if (res >= ADD_OKAY && win.isPresentation()) {
+ mPresentationController.onPresentationAdded(win);
}
}
@@ -4732,11 +4720,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
- dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(visibleTypes, mask);
+ final @InsetsType int changedTypes =
+ dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(
+ visibleTypes, mask);
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
// IME provider. Check if we have to create a new request here, if null.
dc.getInsetsStateController().onRequestedVisibleTypesChanged(
- dc.mRemoteInsetsControlTarget, statsToken);
+ dc.mRemoteInsetsControlTarget, changedTypes, statsToken);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -10315,11 +10305,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
private static boolean getShellTransitEnabled() {
- android.content.pm.FeatureInfo autoFeature = SystemConfig.getInstance()
- .getAvailableFeatures().get(PackageManager.FEATURE_AUTOMOTIVE);
- if (autoFeature != null && autoFeature.version >= 0) {
- return SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true);
- }
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 924b9de5a562..3b6a4dc6e1b0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2455,10 +2455,28 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
/** Whether the configuration changes are important to report back to an organizer. */
static boolean configurationsAreEqualForOrganizer(
Configuration newConfig, @Nullable Configuration oldConfig) {
+ return configurationsAreEqualForOrganizer(newConfig, oldConfig, 0 /* additionalMask */);
+ }
+
+ /**
+ * Whether the configuration changes are important to report back to an organizer.
+ *
+ * @param newConfig the new configuration
+ * @param oldConfig the old configuration
+ * @param additionalMask specifies additional configuration changes that the organizer is
+ * interested in. If the configuration change matches any bit in the mask,
+ * {@code false} is returned.
+ */
+ static boolean configurationsAreEqualForOrganizer(
+ Configuration newConfig, @Nullable Configuration oldConfig,
+ @ActivityInfo.Config int additionalMask) {
if (oldConfig == null) {
return false;
}
int cfgChanges = newConfig.diff(oldConfig);
+ if ((cfgChanges & additionalMask) != 0) {
+ return false;
+ }
final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration,
true /* compareUndefined */) : 0;
@@ -2665,6 +2683,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
ownerActivity.getUid(), ownerActivity.info.processName);
if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+ taskFragment.setConfigurationChangeMaskForOrganizer(
+ creationParams.getConfigurationChangeMask());
}
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f94bbb70befc..589724182980 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -182,7 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -822,17 +821,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
+ * @return an integer as the changed requested visible insets types.
* @see #getRequestedVisibleTypes()
*/
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ @InsetsType int setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ final int changedTypes = mRequestedVisibleTypes ^ requestedVisibleTypes;
mRequestedVisibleTypes = requestedVisibleTypes;
+ return changedTypes;
}
+ return 0;
}
@VisibleForTesting
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes, @InsetsType int mask) {
- setRequestedVisibleTypes(mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
+ @InsetsType int setRequestedVisibleTypes(
+ @InsetsType int requestedVisibleTypes, @InsetsType int mask) {
+ return setRequestedVisibleTypes(
+ mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
}
/**
@@ -2294,15 +2299,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int type = mAttrs.type;
- if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
- // TODO(b/393945496): Make sure that there's one presentation at most per display.
- dc.mIsPresenting = false;
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- dc.ensureActivitiesVisible(/*starting=*/ null, /*notifyClients=*/ true);
- }
- mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
- /*isShown=*/ false);
+ if (isPresentation()) {
+ mWmService.mPresentationController.onPresentationRemoved(this);
}
// Check if window provides non decor insets before clearing its provided insets.
final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
@@ -3331,6 +3329,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ boolean isPresentation() {
+ return mAttrs.type == TYPE_PRESENTATION || mAttrs.type == TYPE_PRIVATE_PRESENTATION;
+ }
+
private boolean isOnVirtualDisplay() {
return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL;
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 2aa0c6b6dd0b..440eae5f7dea 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -446,7 +446,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
@Override
public void binderDied() {
Slog.d(TAG, "Client binder died - clearing session");
- finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode());
+ finishSession(isUiWaitingForData(), ApiStatus.BINDER_DIED.getMetricCode());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index ece729fe5b32..c21e645d797f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -16,6 +16,7 @@
package com.android.server.credentials.metrics;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_BINDER_DIED;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_FAILURE;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_SUCCESS;
@@ -27,7 +28,9 @@ public enum ApiStatus {
CLIENT_CANCELED(
CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED),
USER_CANCELED(
- CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED);
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED),
+ BINDER_DIED(
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_BINDER_DIED);
private final int mInnerMetricCode;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 6e038f9b67a0..ba02122d1dc5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -54,17 +54,7 @@ class EnterpriseSpecificIdCalculator {
TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
- String imei;
- try {
- imei = telephonyService.getImei(0);
- } catch (UnsupportedOperationException doesNotSupportGms) {
- // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
- // However that runs the risk of changing a device's existing ESID if on these devices
- // telephonyService.getImei() actually returns non-null even when the device does not
- // declare FEATURE_TELEPHONY_GSM.
- imei = null;
- }
- mImei = imei;
+ mImei = telephonyService.getImei(0);
String meid;
try {
meid = telephonyService.getMeid(0);
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index dae481a3c215..36947a2a6d62 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -3198,8 +3198,10 @@ void IncrementalService::DataLoaderStub::onDump(int fd) {
dprintf(fd, " }\n");
}
-void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) {
+binder::Status IncrementalService::AppOpsListener::opChanged(int32_t, int32_t,
+ const String16&, const String16&) {
incrementalService.onAppOpChanged(packageName);
+ return binder::Status::ok();
}
binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams(
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index b81e1b1b071c..4ee1a70dc34c 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -26,7 +26,7 @@
#include <android/os/incremental/BnStorageLoadingProgressListener.h>
#include <android/os/incremental/PerUidReadTimeouts.h>
#include <android/os/incremental/StorageHealthCheckParams.h>
-#include <binder/IAppOpsCallback.h>
+#include <binder/AppOpsManager.h>
#include <binder/PersistableBundle.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
@@ -200,11 +200,12 @@ public:
void getMetrics(int32_t storageId, android::os::PersistableBundle* _aidl_return);
- class AppOpsListener : public android::BnAppOpsCallback {
+ class AppOpsListener : public com::android::internal::app::BnAppOpsCallback {
public:
AppOpsListener(IncrementalService& incrementalService, std::string packageName)
: incrementalService(incrementalService), packageName(std::move(packageName)) {}
- void opChanged(int32_t op, const String16& packageName) final;
+ binder::Status opChanged(int32_t op, int32_t uid, const String16& packageName,
+ const String16& persistentDeviceId) final;
private:
IncrementalService& incrementalService;
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 39e2ee324e0c..36a5b7f4a75d 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -23,7 +23,7 @@
#include <android/content/pm/IDataLoader.h>
#include <android/content/pm/IDataLoaderStatusListener.h>
#include <android/os/incremental/PerUidReadTimeouts.h>
-#include <binder/IAppOpsCallback.h>
+#include <binder/AppOpsManager.h>
#include <binder/IServiceManager.h>
#include <binder/Status.h>
#include <incfs.h>
@@ -133,6 +133,7 @@ public:
class AppOpsManagerWrapper {
public:
+ using IAppOpsCallback = ::com::android::internal::app::IAppOpsCallback;
virtual ~AppOpsManagerWrapper() = default;
virtual binder::Status checkPermission(const char* permission, const char* operation,
const char* package) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index d9d3d62e92e2..73849a3e0e00 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -1678,7 +1678,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChang
{}, {}));
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
- mAppOpsManager->mStoredCallback->opChanged(0, {});
+ mAppOpsManager->mStoredCallback->opChanged(0, 0, {}, {});
}
TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c974d9e1dc87..2bbd69c65eb8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -894,6 +894,17 @@ public final class SystemServer implements Dumpable {
SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
+ // Prepare the thread pool for init tasks that can be parallelized
+ SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
+ mDumper.addDumpable(tp);
+
+ if (android.server.Flags.earlySystemConfigInit()) {
+ // SystemConfig init is expensive, so enqueue the work as early as possible to allow
+ // concurrent execution before it's needed (typically by ActivityManagerService).
+ // As native library loading is also expensive, this is a good place to start.
+ startSystemConfigInit(t);
+ }
+
// Initialize native services.
System.loadLibrary("android_servers");
@@ -926,9 +937,6 @@ public final class SystemServer implements Dumpable {
mDumper.addDumpable(mSystemServiceManager);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
- // Prepare the thread pool for init tasks that can be parallelized
- SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
- mDumper.addDumpable(tp);
// Lazily load the pre-installed system font map in SystemServer only if we're not doing
// the optimized font loading in the FontManagerService.
@@ -1093,6 +1101,14 @@ public final class SystemServer implements Dumpable {
}
}
+ private void startSystemConfigInit(TimingsTraceAndSlog t) {
+ Slog.i(TAG, "Reading configuration...");
+ final String tagSystemConfig = "ReadingSystemConfig";
+ t.traceBegin(tagSystemConfig);
+ SystemServerInitThreadPool.submit(SystemConfig::getInstance, tagSystemConfig);
+ t.traceEnd();
+ }
+
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
@@ -1131,11 +1147,11 @@ public final class SystemServer implements Dumpable {
mDumper.addDumpable(watchdog);
t.traceEnd();
- Slog.i(TAG, "Reading configuration...");
- final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
- t.traceBegin(TAG_SYSTEM_CONFIG);
- SystemServerInitThreadPool.submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
- t.traceEnd();
+ // Legacy entry point for starting SystemConfig init, only needed if the early init flag is
+ // disabled and we haven't already triggered init before bootstrap services.
+ if (!android.server.Flags.earlySystemConfigInit()) {
+ startSystemConfigInit(t);
+ }
// Orchestrates some ProtoLogging functionality.
if (android.tracing.Flags.clientSideProtoLogging()) {
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 4d021ec2c0d3..86ccd878de7c 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -10,6 +10,13 @@ flag {
}
flag {
+ namespace: "system_performance"
+ name: "early_system_config_init"
+ description: "Perform earlier initialization of SystemConfig in system server startup."
+ bug: "383869534"
+}
+
+flag {
name: "remove_text_service"
namespace: "wear_frameworks"
description: "Remove TextServiceManagerService on Wear"
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index a103b0583eac..a7280c2167ea 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -31,7 +31,6 @@ import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.RemoteException;
@@ -42,7 +41,6 @@ import android.provider.Settings;
import android.server.wm.WindowManagerStateHelper;
import android.util.Log;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
@@ -59,6 +57,7 @@ import androidx.test.uiautomator.Until;
import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+import com.android.compatibility.common.util.GestureNavSwitchHelper;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
@@ -90,6 +89,8 @@ public class InputMethodServiceTest {
private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+ private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
+
private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
@Rule
@@ -100,7 +101,6 @@ public class InputMethodServiceTest {
private Instrumentation mInstrumentation;
private UiDevice mUiDevice;
- private Context mContext;
private InputMethodManager mImm;
private String mTargetPackageName;
private String mInputMethodId;
@@ -112,8 +112,7 @@ public class InputMethodServiceTest {
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mUiDevice = UiDevice.getInstance(mInstrumentation);
- mContext = mInstrumentation.getContext();
- mImm = mContext.getSystemService(InputMethodManager.class);
+ mImm = mInstrumentation.getContext().getSystemService(InputMethodManager.class);
mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
mInputMethodId = getInputMethodId();
prepareIme();
@@ -872,35 +871,47 @@ public class InputMethodServiceTest {
* Verifies that clicking on the IME navigation bar back button hides the IME.
*/
@Test
- public void testBackButtonClick() {
+ public void testBackButtonClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ final boolean isGestureMode = mGestureNavSwitchHelper.isGestureMode();
- final var backButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButtonUiObject.click();
- mInstrumentation.waitForIdleSync();
+ final var restoreNav = new AutoCloseable[]{() -> {}};
+ try {
+ if (!isGestureMode) {
+ // Wait for onConfigurationChanged when changing navigation modes.
+ verifyInputViewStatus(
+ () -> restoreNav[0] = mGestureNavSwitchHelper.withGestureNavigationMode(),
+ true, /* expected */
+ false /* inputViewStarted */
+ );
+ }
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithWindowInsetsController(),
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
+ backButton.click();
+ mInstrumentation.waitForIdleSync();
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have
+ // to wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse();
+ }
+ } finally {
+ restoreNav[0].close();
}
}
@@ -908,35 +919,47 @@ public class InputMethodServiceTest {
* Verifies that long clicking on the IME navigation bar back button hides the IME.
*/
@Test
- public void testBackButtonLongClick() {
+ public void testBackButtonLongClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ final boolean isGestureMode = mGestureNavSwitchHelper.isGestureMode();
- final var backButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButtonUiObject.longClick();
- mInstrumentation.waitForIdleSync();
+ final var restoreNav = new AutoCloseable[]{() -> {}};
+ try {
+ if (!isGestureMode) {
+ // Wait for onConfigurationChanged when changing navigation modes.
+ verifyInputViewStatus(
+ () -> restoreNav[0] = mGestureNavSwitchHelper.withGestureNavigationMode(),
+ true, /* expected */
+ false /* inputViewStarted */
+ );
+ }
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithWindowInsetsController(),
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
+ backButton.longClick();
+ mInstrumentation.waitForIdleSync();
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have
+ // to wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse();
+ }
+ } finally {
+ restoreNav[0].close();
}
}
@@ -945,74 +968,104 @@ public class InputMethodServiceTest {
* or switches the input method.
*/
@Test
- public void testImeSwitchButtonClick() {
+ public void testImeSwitchButtonClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ final boolean isGestureMode = mGestureNavSwitchHelper.isGestureMode();
- final var initialInfo = mImm.getCurrentInputMethodInfo();
+ final var restoreNav = new AutoCloseable[]{() -> {}};
+ try {
+ if (!isGestureMode) {
+ // Wait for onConfigurationChanged when changing navigation modes.
+ verifyInputViewStatus(
+ () -> restoreNav[0] = mGestureNavSwitchHelper.withGestureNavigationMode(),
+ true, /* expected */
+ false /* inputViewStarted */
+ );
+ }
- final var imeSwitchButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
- imeSwitchButtonUiObject.click();
- mInstrumentation.waitForIdleSync();
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ mActivity.showImeWithWindowInsetsController();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
- final var newInfo = mImm.getCurrentInputMethodInfo();
+ final var initialInfo = mImm.getCurrentInputMethodInfo();
- assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
- .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
- .isTrue();
+ final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
+ imeSwitcherButton.click();
+ mInstrumentation.waitForIdleSync();
- assertWithMessage("IME is still shown after IME Switcher button was clicked")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ final var newInfo = mImm.getCurrentInputMethodInfo();
+
+ assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
+ .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
+ .isTrue();
+
+ assertWithMessage("IME is still shown after IME Switcher button was clicked")
+ .that(mInputMethodService.isInputViewShown()).isTrue();
- // Hide the IME Switcher Menu before finishing.
- mUiDevice.pressBack();
+ // Hide the IME Switcher Menu before finishing.
+ mUiDevice.pressBack();
+ } finally {
+ restoreNav[0].close();
+ }
}
/**
* Verifies that long clicking on the IME switch button shows the Input Method Switcher Menu.
*/
@Test
- public void testImeSwitchButtonLongClick() {
+ public void testImeSwitchButtonLongClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ final boolean isGestureMode = mGestureNavSwitchHelper.isGestureMode();
- final var imeSwitchButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
- imeSwitchButtonUiObject.longClick();
- mInstrumentation.waitForIdleSync();
+ final var restoreNav = new AutoCloseable[]{() -> {}};
+ try {
+ if (!isGestureMode) {
+ // Wait for onConfigurationChanged when changing navigation modes.
+ verifyInputViewStatus(
+ () -> restoreNav[0] = mGestureNavSwitchHelper.withGestureNavigationMode(),
+ true, /* expected */
+ false /* inputViewStarted */
+ );
+ }
- assertWithMessage("Input Method Switcher Menu is shown")
- .that(isInputMethodPickerShown(mImm)).isTrue();
- assertWithMessage("IME is still shown after IME Switcher button was long clicked")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ mActivity.showImeWithWindowInsetsController();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
+ imeSwitcherButton.longClick();
+ mInstrumentation.waitForIdleSync();
- // Hide the IME Switcher Menu before finishing.
- mUiDevice.pressBack();
+ assertWithMessage("Input Method Switcher Menu is shown")
+ .that(isInputMethodPickerShown(mImm)).isTrue();
+ assertWithMessage("IME is still shown after IME Switcher button was long clicked")
+ .that(mInputMethodService.isInputViewShown()).isTrue();
+
+ // Hide the IME Switcher Menu before finishing.
+ mUiDevice.pressBack();
+ } finally {
+ restoreNav[0].close();
+ }
}
private void verifyInputViewStatus(@NonNull Runnable runnable, boolean expected,
@@ -1105,6 +1158,9 @@ public class InputMethodServiceTest {
// Get the new TestActivity.
mActivity = TestActivity.getLastCreatedInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
+ // Wait for the new EditText to be served by InputMethodManager.
+ eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
+ .that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
}
verifyInputViewStatusOnMainSync(
@@ -1214,18 +1270,12 @@ public class InputMethodServiceTest {
return uiObject;
}
- /** Checks whether gesture navigation move is enabled. */
- private boolean isGestureNavEnabled() {
- return mContext.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode)
- == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
- }
-
/** Checks whether the device has a navigation bar on the IME's display. */
private boolean hasNavigationBar() {
try {
return WindowManagerGlobal.getWindowManagerService()
- .hasNavigationBar(mInputMethodService.getDisplayId());
+ .hasNavigationBar(mInputMethodService.getDisplayId())
+ && mGestureNavSwitchHelper.hasNavigationBar();
} catch (RemoteException e) {
fail("Failed to check whether the device has a navigation bar: " + e.getMessage());
return false;
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index cf7d660a68ef..00873de4aaed 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -42,6 +42,7 @@
<activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
android:exported="false"
android:label="TestActivity"
+ android:configChanges="assetsPaths"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:noHistory="true"
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 f154dbcee21a..09ce263e9b2f 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
@@ -3962,7 +3962,7 @@ public class DisplayModeDirectorTest {
}
@Override
- public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ public VotesStatsReporter getVotesStatsReporter() {
return null;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
index 1e665c2c5c50..409706b14c56 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
@@ -1550,6 +1550,118 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2));
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryGroupPolicy_sameAction_multiplePolicies() {
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in the whole PACKAGE_GREEN
+ // package.
+ final Intent greenPackageChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN), List.of(PACKAGE_GREEN));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "com.example.green/10002", only the most recent one
+ // gets delivered and the rest get discarded.
+ final BroadcastOptions optionsMostRecentPolicyForPackageGreen =
+ BroadcastOptions.makeBasic();
+ optionsMostRecentPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+ PACKAGE_GREEN + "/" + getUidForPackage(PACKAGE_GREEN));
+ optionsMostRecentPolicyForPackageGreen.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in the whole PACKAGE_RED
+ // package.
+ final Intent redPackageChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_RED), List.of(PACKAGE_RED));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "com.example.red/10001", only the most recent one
+ // gets delivered and the rest get discarded.
+ final BroadcastOptions optionsMostRecentPolicyForPackageRed =
+ BroadcastOptions.makeBasic();
+ optionsMostRecentPolicyForPackageRed.setDeliveryGroupMatchingKey("package_changed",
+ PACKAGE_RED + "/" + getUidForPackage(PACKAGE_RED));
+ optionsMostRecentPolicyForPackageRed.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in some components of
+ // PACKAGE_GREEN package.
+ final Intent greenPackageComponentsChangedIntent1 = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN),
+ List.of(PACKAGE_GREEN + ".comp1", PACKAGE_GREEN + ".comp2"));
+ final Intent greenPackageComponentsChangedIntent2 = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN),
+ List.of(PACKAGE_GREEN + ".comp3"));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "components-com.example.green/10002", merge the extras
+ // within these broadcasts such that only one broadcast is sent and the rest are
+ // discarded. Couple of things to note here:
+ // 1. We are intentionally using a different policy group
+ // "components-com.example.green/10002" (as opposed to "com.example.green/10002" used
+ // earlier), because this is corresponding to a change in some particular components,
+ // rather than a change to the whole package and we want to keep these two types of
+ // broadcasts independent.
+ // 2. We are using 'extrasMerger' to indicate how we want the extras to be merged. This
+ // assumes that broadcasts belonging to the group 'components-com.example.green/10002'
+ // will have the same values for all the extras, except for the one extra
+ // 'EXTRA_CHANGED_COMPONENT_NAME_LIST'. So, we explicitly specify how to merge this
+ // extra by using 'STRATEGY_ARRAY_APPEND' strategy, which basically indicates that
+ // the extra values which are arrays should be concatenated.
+ final BundleMerger extrasMerger = new BundleMerger();
+ extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ BundleMerger.STRATEGY_ARRAY_APPEND);
+ final BroadcastOptions optionsMergedPolicyForPackageGreen = BroadcastOptions.makeBasic();
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+ "components-" + PACKAGE_GREEN + "/" + getUidForPackage(PACKAGE_GREEN));
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in some components of
+ // PACKAGE_RED package.
+ final Intent redPackageComponentsChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_RED),
+ List.of(PACKAGE_RED + ".comp1", PACKAGE_RED + ".comp2"));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "components-com.example.red/10001", merge the extras
+ // within these broadcasts such that only one broadcast is sent and the rest are
+ // discarded.
+ final BroadcastOptions optionsMergedPolicyForPackageRed = BroadcastOptions.makeBasic();
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+ "components-" + PACKAGE_RED + "/" + getUidForPackage(PACKAGE_RED));
+ optionsMergedPolicyForPackageRed.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsMergedPolicyForPackageRed.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageChangedIntent,
+ optionsMostRecentPolicyForPackageGreen));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(redPackageChangedIntent,
+ optionsMostRecentPolicyForPackageRed));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageComponentsChangedIntent1,
+ optionsMergedPolicyForPackageGreen));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(redPackageComponentsChangedIntent,
+ optionsMergedPolicyForPackageRed));
+ // Since this broadcast has DELIVERY_GROUP_MOST_RECENT policy set, the earlier
+ // greenPackageChangedIntent broadcast with the same policy will be discarded.
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageChangedIntent,
+ optionsMostRecentPolicyForPackageGreen));
+ // Since this broadcast has DELIVERY_GROUP_MERGED policy set, the earlier
+ // greenPackageComponentsChangedIntent1 broadcast with the same policy will be merged
+ // with this one and then will be discarded.
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageComponentsChangedIntent2,
+ optionsMergedPolicyForPackageGreen));
+
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // The extra EXTRA_CHANGED_COMPONENT_NAME_LIST values from
+ // greenPackageComponentsChangedIntent1 and
+ // greenPackageComponentsChangedIntent2 broadcasts would be merged, since
+ // STRATEGY_ARRAY_APPEND was used for this extra.
+ final Intent expectedGreenPackageComponentsChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN), List.of(PACKAGE_GREEN + ".comp3",
+ PACKAGE_GREEN + ".comp1", PACKAGE_GREEN + ".comp2"));
+ verifyPendingRecords(queue, List.of(redPackageChangedIntent,
+ redPackageComponentsChangedIntent, greenPackageChangedIntent,
+ expectedGreenPackageComponentsChangedIntent));
+ }
+
private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs,
int droppedCount) {
final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index f79cb1105611..360d6ebfe1bd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,6 +19,11 @@ import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.multiuser.Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
+import static android.multiuser.Flags.FLAG_LOGOUT_USER_API;
+import static android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -54,7 +59,6 @@ import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.multiuser.Flags;
import android.os.PowerManager;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
@@ -401,15 +405,27 @@ public final class UserManagerServiceTest {
}
@Test
- public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
+ public void testGetBootUser_CannotSwitchToHeadlessSystemUser_ThrowsIfOnlySystemUserExists()
+ throws Exception {
setSystemUserHeadless(true);
removeNonSystemUsers();
+ mockCanSwitchToHeadlessSystemUser(false);
assertThrows(UserManager.CheckedUserOperationException.class,
() -> mUmi.getBootUser(/* waitUntilSet= */ false));
}
@Test
+ public void testGetBootUser_CanSwitchToHeadlessSystemUser_NoThrowIfOnlySystemUserExists()
+ throws Exception {
+ setSystemUserHeadless(true);
+ removeNonSystemUsers();
+ mockCanSwitchToHeadlessSystemUser(true);
+
+ assertThat(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
public void testGetPreviousFullUserToEnterForeground() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
@@ -601,9 +617,8 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testAutoLockPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -622,10 +637,12 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testAutoLockOnDeviceLockForPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -645,10 +662,12 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
assumeTrue(mUms.canAddPrivateProfile(0));
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
@@ -665,10 +684,9 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @DisableFlags(FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE)
public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -687,10 +705,12 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testAutoLockAfterInactityForPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -711,11 +731,12 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
-
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
@@ -726,10 +747,12 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testSetOrUpdateAutoLockPreference() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
@@ -780,10 +803,12 @@ public final class UserManagerServiceTest {
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES
+ })
public void testGetProfileIdsExcludingHidden() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
assumeTrue(mUms.canAddPrivateProfile(0));
UserInfo privateProfileUser =
mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
@@ -794,8 +819,11 @@ public final class UserManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
UserManagerService mSpiedUms = spy(mUms);
assumeTrue(mUms.isHeadlessSystemUserMode());
@@ -807,8 +835,11 @@ public final class UserManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
assumeTrue(mUms.canAddMoreUsersOfType(USER_TYPE_FULL_SECONDARY));
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
@@ -819,8 +850,11 @@ public final class UserManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
int mainUser = mUms.getMainUserId();
@@ -831,8 +865,11 @@ public final class UserManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
int mainUser = mUms.getMainUserId();
@@ -843,8 +880,11 @@ public final class UserManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
int mainUser = mUms.getMainUserId();
@@ -855,8 +895,11 @@ public final class UserManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
int mainUser = mUms.getMainUserId();
@@ -910,7 +953,7 @@ public final class UserManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_HsumAndInteractiveHeadlessSystem_UserCanLogout()
throws Exception {
setSystemUserHeadless(true);
@@ -926,7 +969,7 @@ public final class UserManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_HsumAndNonInteractiveHeadlessSystem_UserCannotLogout()
throws Exception {
setSystemUserHeadless(true);
@@ -941,7 +984,7 @@ public final class UserManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_Hsum_SystemUserCannotLogout() throws Exception {
setSystemUserHeadless(true);
mockCurrentUser(UserHandle.USER_SYSTEM);
@@ -950,7 +993,7 @@ public final class UserManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_NonHsum_SystemUserCannotLogout() throws Exception {
setSystemUserHeadless(false);
mockCurrentUser(UserHandle.USER_SYSTEM);
@@ -960,7 +1003,7 @@ public final class UserManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_CannotSwitch_CannotLogout() throws Exception {
setSystemUserHeadless(true);
addUser(USER_ID);
@@ -973,7 +1016,7 @@ public final class UserManagerServiceTest {
}
@Test
- @DisableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @DisableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_LogoutDisabled() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> mUms.getUserLogoutability(USER_ID));
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index 7b8824cb0e3d..00cc7264c1d0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -18,6 +18,11 @@ package com.android.server.accessibility.autoclick;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@@ -59,11 +64,25 @@ public class AutoclickTypePanelTest {
private LinearLayout mDragButton;
private LinearLayout mScrollButton;
+ private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+
+ private final ClickPanelControllerInterface clickPanelController =
+ new ClickPanelControllerInterface() {
+ @Override
+ public void handleAutoclickTypeChange(@AutoclickType int clickType) {
+ mActiveClickType = clickType;
+ }
+
+ @Override
+ public void toggleAutoclickPause() {}
+ };
+
@Before
public void setUp() {
mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
- mAutoclickTypePanel = new AutoclickTypePanel(mTestableContext, mMockWindowManager);
+ mAutoclickTypePanel =
+ new AutoclickTypePanel(mTestableContext, mMockWindowManager, clickPanelController);
View contentView = mAutoclickTypePanel.getContentViewForTesting();
mLeftClickButton = contentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
mRightClickButton =
@@ -136,6 +155,17 @@ public class AutoclickTypePanelTest {
verifyButtonHasSelectedStyle(mScrollButton);
}
+ @Test
+ public void togglePanelExpansion_selectButton_correctActiveClickType() {
+ // By first click, the panel is expanded.
+ mLeftClickButton.callOnClick();
+
+ // Clicks any button in the expanded state to select a type button.
+ mScrollButton.callOnClick();
+
+ assertThat(mActiveClickType).isEqualTo(AUTOCLICK_TYPE_SCROLL);
+ }
+
private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
assertThat(gradientDrawable.getColor().getDefaultColor())
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 17f5ebb3b02a..7349c5f463a6 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -116,6 +116,10 @@ public class GameManagerServiceSettingsTests {
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
+ static {
+ System.loadLibrary("servicestestjni");
+ }
+
@Test
public void testReadGameServiceSettings() {
writeOldFiles();
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 04d075211297..a4e77c00d647 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,10 +20,12 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier;
@@ -32,6 +34,7 @@ import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.IEndpointCommunication;
import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
import android.os.Binder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +64,9 @@ public class ContextHubEndpointTest {
private static final int ENDPOINT_ID = 1;
private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";
+ private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
+ private static final int TARGET_ENDPOINT_ID = 1;
+
private ContextHubClientManager mClientManager;
private ContextHubEndpointManager mEndpointManager;
private HubInfoRegistry mHubInfoRegistry;
@@ -95,23 +101,8 @@ public class ContextHubEndpointTest {
@Test
public void testRegisterEndpoint() throws RemoteException {
- // Register an endpoint and confirm we can get a valid IContextHubEndoint reference
- HubEndpointInfo info =
- new HubEndpointInfo(
- ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
- IContextHubEndpoint endpoint =
- mEndpointManager.registerEndpoint(
- info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
- assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
- assertThat(endpoint).isNotNull();
- HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
- assertThat(assignedInfo).isNotNull();
- HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
- assertThat(assignedIdentifier).isNotNull();
-
- // Unregister the endpoint and confirm proper clean-up
- mEndpointManager.unregisterEndpoint(assignedIdentifier.getEndpoint());
- assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ unregisterExampleEndpoint(endpoint);
}
@Test
@@ -146,4 +137,107 @@ public class ContextHubEndpointTest {
assertThat(statusCaptor.getValue().messageSequenceNumber).isEqualTo(sequenceNumber);
assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.DESTINATION_NOT_FOUND);
}
+
+ @Test
+ public void testHalRestart() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ // Verify that the endpoint is still registered after a HAL restart
+ HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+ HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+ mEndpointManager.onHalRestart();
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testHalRestartOnOpenSession() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+ mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);
+
+ mEndpointManager.onHalRestart();
+
+ HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+ HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+
+ verify(mMockCallback)
+ .onSessionClosed(
+ sessionId, ContextHubServiceUtil.toAppHubEndpointReason(Reason.HUB_RESET));
+
+ unregisterExampleEndpoint(endpoint);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ }
+
+ @Test
+ public void testOpenSessionOnUnregistration() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+ mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);
+
+ unregisterExampleEndpoint(endpoint);
+ verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.ENDPOINT_GONE);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ }
+
+ private IContextHubEndpoint registerExampleEndpoint() throws RemoteException {
+ HubEndpointInfo info =
+ new HubEndpointInfo(
+ ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
+ IContextHubEndpoint endpoint =
+ mEndpointManager.registerEndpoint(
+ info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
+ assertThat(endpoint).isNotNull();
+ HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+ assertThat(assignedInfo).isNotNull();
+ HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+ assertThat(assignedIdentifier).isNotNull();
+
+ // Confirm registerEndpoint was called with the right contents
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications).registerEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+ assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
+
+ return endpoint;
+ }
+
+ private void unregisterExampleEndpoint(IContextHubEndpoint endpoint) throws RemoteException {
+ HubEndpointInfo expectedInfo = endpoint.getAssignedHubEndpointInfo();
+ endpoint.unregister();
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications).unregisterEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id)
+ .isEqualTo(expectedInfo.getIdentifier().getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId)
+ .isEqualTo(expectedInfo.getIdentifier().getHub());
+ assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index 770712a191fd..41011928f8b3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -204,7 +204,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase {
.build());
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
- .setCategory(CATEGORY_ALARM)
+ .setCategory(new String("alarm"))
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.build();
NotificationRecord r = getRecord(channel, n);
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 440f43e9b926..bb296148dad1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2881,7 +2881,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity2.addStartingWindow(mPackageName, android.R.style.Theme, activity1, true, true,
false, true, false, false, false);
waitUntilHandlersIdle();
- assertFalse(mDisplayContent.mSkipAppTransitionAnimation);
assertNoStartingWindow(activity1);
assertHasStartingWindow(activity2);
}
@@ -2965,7 +2964,6 @@ public class ActivityRecordTests extends WindowTestsBase {
false /* newTask */, false /* isTaskSwitch */, null /* options */,
null /* sourceRecord */);
- assertTrue(mDisplayContent.mSkipAppTransitionAnimation);
assertNull(middle.mStartingWindow);
assertHasStartingWindow(top);
assertTrue(top.isVisible());
@@ -3265,26 +3263,6 @@ public class ActivityRecordTests extends WindowTestsBase {
> activity.getConfiguration().windowConfiguration.getAppBounds().height());
}
- @Test
- public void testSetVisibility_visibleToVisible() {
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true).build();
- // By default, activity is visible.
- assertTrue(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-
- // Request the activity to be visible. Although the activity is already visible, app
- // transition animation should be applied on this activity. This might be unnecessary, but
- // until we verify no logic relies on this behavior, we'll keep this as is.
- mDisplayContent.prepareAppTransition(0);
- activity.setVisibility(true);
- assertTrue(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
- }
-
@SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testSetVisibility_visibleToInvisible() {
@@ -3316,50 +3294,30 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetVisibility_invisibleToVisible() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true).setVisible(false).build();
- // Activiby is invisible. However ATMS requests it to become visible, since this is a top
- // activity.
assertFalse(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ assertFalse(activity.isVisibleRequested());
// Request the activity to be visible. Since the visibility changes, app transition
// animation should be applied on this activity.
- activity.setVisibility(true);
+ requestTransition(activity, WindowManager.TRANSIT_OPEN);
+ mWm.mRoot.resumeFocusedTasksTopActivities();
assertFalse(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-
- // There should still be animation (add to opening) if keyguard is going away while the
- // screen is off because it will be visible after screen is turned on by unlocking.
- mDisplayContent.mOpeningApps.remove(activity);
- mDisplayContent.mClosingApps.remove(activity);
- activity.commitVisibility(false /* visible */, false /* performLayout */);
- mDisplayContent.getDisplayPolicy().screenTurnedOff(false /* acquireSleepToken */);
- final KeyguardController controller = mSupervisor.getKeyguardController();
- doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
- activity.setVisibility(true);
- assertTrue(mDisplayContent.mOpeningApps.contains(activity));
+ assertTrue(activity.inTransition());
}
@Test
public void testSetVisibility_invisibleToInvisible() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true).setVisible(false).build();
- // Activiby is invisible. However ATMS requests it to become visible, since this is a top
- // activity.
- assertFalse(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ requestTransition(activity, WindowManager.TRANSIT_CLOSE);
// Request the activity to be invisible. Since the activity is already invisible, no app
// transition should be applied on this activity.
activity.setVisibility(false);
assertFalse(activity.isVisible());
assertFalse(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ assertFalse(activity.inTransition());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
deleted file mode 100644
index 8871056988ef..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for the {@link ActivityStack} class.
- *
- * Build/Install/Run:
- * atest WmTests:AnimatingActivityRegistryTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AnimatingActivityRegistryTest extends WindowTestsBase {
-
- @Mock
- AnimationAdapter mAdapter;
-
- @Mock
- Runnable mMockEndDeferFinishCallback1;
- @Mock
- Runnable mMockEndDeferFinishCallback2;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testDeferring() {
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- final ActivityRecord activity2 = createAppWindow(activity1.getTask(), ACTIVITY_TYPE_STANDARD,
- "activity2").mActivityRecord;
- final AnimatingActivityRegistry registry =
- activity1.getRootTask().getAnimatingActivityRegistry();
-
- activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- assertTrue(activity1.isAnimating(TRANSITION));
- assertTrue(activity2.isAnimating(TRANSITION));
-
- // Make sure that first animation finish is deferred, second one is not deferred, and first
- // one gets cancelled.
- assertTrue(registry.notifyAboutToFinish(activity1, mMockEndDeferFinishCallback1));
- assertFalse(registry.notifyAboutToFinish(activity2, mMockEndDeferFinishCallback2));
- verify(mMockEndDeferFinishCallback1).run();
- verifyZeroInteractions(mMockEndDeferFinishCallback2);
- }
-
- @Test
- public void testContainerRemoved() {
- final ActivityRecord window1 = createActivityRecord(mDisplayContent);
- final ActivityRecord window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD,
- "window2").mActivityRecord;
- final AnimatingActivityRegistry registry =
- window1.getRootTask().getAnimatingActivityRegistry();
-
- window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- assertTrue(window1.isAnimating(TRANSITION));
- assertTrue(window2.isAnimating(TRANSITION));
-
- // Make sure that first animation finish is deferred, and removing the second window stops
- // finishes all pending deferred finishings.
- registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1);
- window2.setParent(null);
- verify(mMockEndDeferFinishCallback1).run();
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
deleted file mode 100644
index c294bc62c7ac..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.gui.DropInputMode;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-import android.window.ITaskFragmentOrganizer;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Build/Install/Run:
- * atest WmTests:AppTransitionControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppTransitionControllerTest extends WindowTestsBase {
-
- private AppTransitionController mAppTransitionController;
-
- @Before
- public void setUp() throws Exception {
- assumeFalse(WindowManagerService.sEnableShellTransitions);
- mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
- mWm.mAnimator.ready();
- }
-
- @Test
- public void testSkipOccludedActivityCloseTransition() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord topOpening = createActivityRecord(behind.getTask());
- topOpening.setOccludesParent(true);
- topOpening.setVisible(true);
-
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(behind);
-
- assertEquals(WindowManager.TRANSIT_OLD_UNSET,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testClearTaskSkipAppExecuteTransition() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final Task task = behind.getTask();
- final ActivityRecord top = createActivityRecord(task);
- top.setState(ActivityRecord.State.RESUMED, "test");
- behind.setState(ActivityRecord.State.STARTED, "test");
- behind.setVisibleRequested(true);
-
- task.removeActivities("test", false /* excludingTaskOverlay */);
- assertFalse(mDisplayContent.mAppTransition.isReady());
- }
-
- @Test
- public void testTranslucentOpen() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- doReturn(false).when(translucentOpening).fillsParent();
- translucentOpening.setVisible(false);
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(behind);
- mDisplayContent.mOpeningApps.add(translucentOpening);
-
- assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testTranslucentClose() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord translucentClosing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- doReturn(false).when(translucentClosing).fillsParent();
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(translucentClosing);
- assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testDreamActivityOpenTransition() {
- final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(dreamActivity);
-
- assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testDreamActivityCloseTransition() {
- final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(dreamActivity);
-
- assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_CLOSE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testChangeIsNotOverwritten() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- translucentOpening.setOccludesParent(false);
- translucentOpening.setVisible(false);
- mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
- mDisplayContent.mOpeningApps.add(behind);
- mDisplayContent.mOpeningApps.add(translucentOpening);
- mDisplayContent.mChangingContainers.add(translucentOpening.getTask());
- assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testTransitWithinTask() {
- final ActivityRecord opening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- opening.setOccludesParent(false);
- final ActivityRecord closing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- closing.setOccludesParent(false);
- final Task task = opening.getTask();
- mDisplayContent.mOpeningApps.add(opening);
- mDisplayContent.mClosingApps.add(closing);
- assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_ACTIVITY_OPEN, task));
- closing.getTask().removeChild(closing);
- task.addChild(closing, 0);
- assertTrue(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_ACTIVITY_OPEN, task));
- assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_TASK_OPEN, task));
- }
-
-
- @Test
- public void testIntraWallpaper_open() {
- final ActivityRecord opening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- opening.setVisible(false);
- final WindowManager.LayoutParams attrOpening = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperOpening");
- attrOpening.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowOpening = createWindowState(attrOpening, opening);
- opening.addWindow(appWindowOpening);
-
- final ActivityRecord closing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowManager.LayoutParams attrClosing = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperClosing");
- attrClosing.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowClosing = createWindowState(attrClosing, closing);
- closing.addWindow(appWindowClosing);
-
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(opening);
- mDisplayContent.mClosingApps.add(closing);
-
- assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, appWindowClosing, null, false));
- }
-
- @Test
- public void testIntraWallpaper_toFront() {
- final ActivityRecord opening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- opening.setVisible(false);
- final WindowManager.LayoutParams attrOpening = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperOpening");
- attrOpening.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowOpening = createWindowState(attrOpening, opening);
- opening.addWindow(appWindowOpening);
-
- final ActivityRecord closing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowManager.LayoutParams attrClosing = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperClosing");
- attrClosing.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowClosing = createWindowState(attrClosing, closing);
- closing.addWindow(appWindowClosing);
-
- mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
- mDisplayContent.mOpeningApps.add(opening);
- mDisplayContent.mClosingApps.add(closing);
-
- assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, appWindowClosing, null, false));
- }
-
- @Test
- public void testGetAnimationTargets_visibilityAlreadyUpdated() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
- // +- [Task2] - [ActivityRecord2] (closing, invisible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // No animation, since visibility of the opening and closing apps are already updated
- // outside of AppTransition framework.
- assertEquals(
- new ArraySet<>(),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_visibilityAlreadyUpdated_butForcedTransitionRequested() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (closing, invisible)
- // +- [Task2] - [ActivityRecord2] (opening, visible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(true);
- activity1.setVisibleRequested(true);
- activity1.mRequestForceTransition = true;
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
- activity2.mRequestForceTransition = true;
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // The visibility are already updated, but since forced transition is requested, it will
- // be included.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_exitingBeforeTransition() {
- // Create another non-empty task so the animation target won't promote to task display area.
- createActivityRecord(mDisplayContent);
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- activity.setVisible(false);
- activity.mIsExiting = true;
-
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity);
-
- // Animate closing apps even if it's not visible when it is exiting before we had a chance
- // to play the transition animation.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- new ArraySet<>(), closing, false /* visible */));
- }
-
- @Test
- public void testExitAnimationDone_beforeAppTransition() {
- final Task task = createTask(mDisplayContent);
- final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win");
- spyOn(win);
- win.mAnimatingExit = true;
- mDisplayContent.mAppTransition.setTimeout();
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
- verify(win).onExitAnimationDone();
- }
-
- @Test
- public void testGetAnimationTargets_openingClosingInDifferentTask() {
- // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
- // | +- [ActivityRecord2] (invisible)
- // |
- // +- [Task2] -+- [ActivityRecord3] (closing, visible)
- // +- [ActivityRecord4] (invisible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
-
- final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
- final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
- activity3.getTask());
- activity4.setVisible(false);
- activity4.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity3);
-
- // Promote animation targets to root Task level. Invisible ActivityRecords don't affect
- // promotion decision.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity3.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_openingClosingInSameTask() {
- // [DisplayContent] - [Task] -+- [ActivityRecord1] (opening, invisible)
- // +- [ActivityRecord2] (closing, visible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Don't promote an animation target to Task level, since the same task contains both
- // opening and closing app.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_animateOnlyTranslucentApp() {
- // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
- // | +- [ActivityRecord2] (visible)
- // |
- // +- [Task2] -+- [ActivityRecord3] (closing, visible)
- // +- [ActivityRecord4] (visible)
-
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- activity1.setOccludesParent(false);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
-
- final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
- activity3.setOccludesParent(false);
- final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
- activity3.getTask());
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity3);
-
- // Don't promote an animation target to Task level, since opening (closing) app is
- // translucent and is displayed over other non-animating app.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity3}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_animateTranslucentAndOpaqueApps() {
- // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
- // | +- [ActivityRecord2] (opening, invisible)
- // |
- // +- [Task2] -+- [ActivityRecord3] (closing, visible)
- // +- [ActivityRecord4] (closing, visible)
-
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- activity1.setOccludesParent(false);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
- activity3.setOccludesParent(false);
- final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
- activity3.getTask());
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity3);
- closing.add(activity4);
-
- // Promote animation targets to TaskStack level even though opening (closing) app is
- // translucent as long as all visible siblings animate at the same time.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity3.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_taskContainsMultipleTasks() {
- // [DisplayContent] - [Task] -+- [Task1] - [ActivityRecord1] (opening, invisible)
- // +- [Task2] - [ActivityRecord2] (closing, visible)
- final Task parentTask = createTask(mDisplayContent);
- final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Promote animation targets up to Task level, not beyond.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_splitScreenOpening() {
- // [DisplayContent] - [Task] -+- [split task 1] -+- [Task1] - [AR1] (opening, invisible)
- // +- [split task 2] -+- [Task2] - [AR2] (opening, invisible)
- final Task singleTopRoot = createTask(mDisplayContent);
- final TaskBuilder builder = new TaskBuilder(mSupervisor)
- .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
- .setParentTask(singleTopRoot)
- .setCreatedByOrganizer(true);
- final Task splitRoot1 = builder.build();
- final Task splitRoot2 = builder.build();
- splitRoot1.setAdjacentTaskFragment(splitRoot2);
- final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
-
- // Promote animation targets up to Task level, not beyond.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{splitRoot1, splitRoot2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_openingClosingTaskFragment() {
- // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
- // +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
- final Task parentTask = createTask(mDisplayContent);
- final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
-
- final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
- activity2.setVisible(true);
- activity2.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Promote animation targets up to TaskFragment level, not beyond.
- assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_openingTheOnlyTaskFragmentInTask() {
- // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (opening, invisible)
- // +- [Task2] - [ActivityRecord2] (closing, visible)
- final Task task1 = createTask(mDisplayContent);
- final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
- final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(true);
- activity2.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Promote animation targets up to leaf Task level because there's only one TaskFragment in
- // the Task.
- assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_closingTheOnlyTaskFragmentInTask() {
- // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (closing, visible)
- // +- [Task2] - [ActivityRecord2] (opening, invisible)
- final Task task1 = createTask(mDisplayContent);
- final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
- final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity1.setVisible(true);
- activity1.setVisibleRequested(false);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity1);
-
- // Promote animation targets up to leaf Task level because there's only one TaskFragment in
- // the Task.
- assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_embeddedTask() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, invisible)
- // +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
-
- final Task task2 = createTask(mDisplayContent);
- task2.mRemoveWithTaskOrganizer = true;
- final ActivityRecord activity2 = createActivityRecord(task2);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
-
- // No animation on the embedded task.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
-
- @Test
- public void testGetAnimationTargets_activityInEmbeddedTask() {
- // [DisplayContent] - [Task] (embedded)-+- [ActivityRecord1] (opening, invisible)
- // +- [ActivityRecord2] (closing, visible)
- final Task task = createTask(mDisplayContent);
- task.mRemoveWithTaskOrganizer = true;
-
- final ActivityRecord activity1 = createActivityRecord(task);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecord(task);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Even though embedded task itself doesn't animate, activities in an embedded task
- // animate.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- static class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
- private IRemoteAnimationFinishedCallback mFinishedCallback;
-
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- mFinishedCallback = finishedCallback;
- }
-
- @Override
- public void onAnimationCancelled() throws RemoteException {
- mFinishedCallback = null;
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
- }
-
- boolean isAnimationStarted() {
- return mFinishedCallback != null;
- }
-
- void finishAnimation() {
- try {
- mFinishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- fail();
- }
- }
- }
-
- @Test
- public void testGetRemoteAnimationOverrideEmpty() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- assertNull(mAppTransitionController.getRemoteAnimationOverride(activity,
- TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideWindowContainer() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
- activity.registerRemoteAnimations(definition);
-
- assertEquals(adapter,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- assertNull(mAppTransitionController.getRemoteAnimationOverride(
- null, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideTransitionController() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
- mAppTransitionController.registerRemoteAnimations(definition);
-
- assertEquals(adapter,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- assertEquals(adapter,
- mAppTransitionController.getRemoteAnimationOverride(
- null, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideBoth() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition1 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter1 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition1.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter1);
- activity.registerRemoteAnimations(definition1);
-
- final RemoteAnimationDefinition definition2 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter2 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition2.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, adapter2);
- mAppTransitionController.registerRemoteAnimations(definition2);
-
- assertEquals(adapter2,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_KEYGUARD_UNOCCLUDE, new ArraySet<Integer>()));
- assertEquals(adapter2,
- mAppTransitionController.getRemoteAnimationOverride(
- null, TRANSIT_OLD_KEYGUARD_UNOCCLUDE, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideWindowContainerHasPriority() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition1 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter1 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition1.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter1);
- activity.registerRemoteAnimations(definition1);
-
- final RemoteAnimationDefinition definition2 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter2 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition2.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter2);
- mAppTransitionController.registerRemoteAnimations(definition2);
-
- assertEquals(adapter1,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord activity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(activity);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
- final Task task = createTask(mDisplayContent);
- final ActivityRecord closingActivity = createActivityRecord(task);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
- // Make sure the TaskFragment is not embedded.
- assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- prepareActivityForAppTransition(openingActivity);
- final int uid = 12345;
- closingActivity.info.applicationInfo.uid = uid;
- openingActivity.info.applicationInfo.uid = uid;
- task.effectiveUid = uid;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity,
- null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
-
- // Animation is not run by the remote handler because the activity is filling the Task.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
- final Task task = createTask(mDisplayContent);
- final ActivityRecord closingActivity = createActivityRecord(task);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
- // Make sure the TaskFragment is embedded.
- taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- final Rect embeddedBounds = new Rect(task.getBounds());
- embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
- taskFragment.setBounds(embeddedBounds);
- assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- prepareActivityForAppTransition(openingActivity);
- final int uid = 12345;
- closingActivity.info.applicationInfo.uid = uid;
- openingActivity.info.applicationInfo.uid = uid;
- task.effectiveUid = uid;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity,
- null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing non-embedded activity.
- final ActivityRecord closingActivity = createActivityRecord(task);
- prepareActivityForAppTransition(closingActivity);
- // Opening TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(openingActivity);
- task.effectiveUid = openingActivity.getUid();
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing TaskFragment with embedded activity.
- final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- closingActivity.info.applicationInfo.uid = 12345;
- // Opening TaskFragment with embedded activity with different UID.
- final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
- prepareActivityForAppTransition(openingActivity);
- openingActivity.info.applicationInfo.uid = 54321;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing activity in Task1.
- final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
- prepareActivityForAppTransition(closingActivity);
- // Opening TaskFragment with embedded activity in Task2.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(openingActivity);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation not run by the remote handler.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- closingActivity.info.applicationInfo.uid = 12345;
- task.effectiveUid = closingActivity.getUid();
- // Opening non-embedded activity with different UID.
- final ActivityRecord openingActivity = createActivityRecord(task);
- prepareActivityForAppTransition(openingActivity);
- openingActivity.info.applicationInfo.uid = 54321;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation should not run by the remote handler when there are non-embedded activities of
- // different UID.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord activity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(activity);
- // Set wallpaper as visible.
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
- spyOn(mDisplayContent.mWallpaperController);
- doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation should not run by the remote handler when there is wallpaper in the transition.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
- // one is untrusted embedded.
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(2)
- .setOrganizer(organizer)
- .build();
- final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord();
- final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord();
- // Also create a non-embedded activity in the Task.
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
- task.addChild(activity2, POSITION_BOTTOM);
- prepareActivityForAppTransition(activity0);
- prepareActivityForAppTransition(activity1);
- prepareActivityForAppTransition(activity2);
- doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0);
- doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // The animation will be animated remotely by client and all activities are input disabled
- // for untrusted animation.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- verify(activity0).setDropInputForAnimation(true);
- verify(activity1).setDropInputForAnimation(true);
- verify(activity2).setDropInputForAnimation(true);
- verify(activity0).setDropInputMode(DropInputMode.ALL);
- verify(activity1).setDropInputMode(DropInputMode.ALL);
- verify(activity2).setDropInputMode(DropInputMode.ALL);
-
- // Reset input after animation is finished.
- clearInvocations(activity0);
- clearInvocations(activity1);
- clearInvocations(activity2);
- remoteAnimationRunner.finishAnimation();
-
- verify(activity0).setDropInputForAnimation(false);
- verify(activity1).setDropInputForAnimation(false);
- verify(activity2).setDropInputForAnimation(false);
- verify(activity0).setDropInputMode(DropInputMode.OBSCURED);
- verify(activity1).setDropInputMode(DropInputMode.NONE);
- verify(activity2).setDropInputMode(DropInputMode.NONE);
- }
-
- /**
- * Since we don't have any use case to rely on handling input during animation, disable it even
- * if it is trusted embedding so that it could cover some edge-cases when a previously trusted
- * host starts doing something bad.
- */
- @Test
- public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with only trusted embedded activity
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(1)
- .setOrganizer(organizer)
- .build();
- final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
- prepareActivityForAppTransition(activity);
- doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // The animation will be animated remotely by client and all activities are input disabled
- // for untrusted animation.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- verify(activity).setDropInputForAnimation(true);
- verify(activity).setDropInputMode(DropInputMode.ALL);
-
- // Reset input after animation is finished.
- clearInvocations(activity);
- remoteAnimationRunner.finishAnimation();
-
- verify(activity).setDropInputForAnimation(false);
- verify(activity).setDropInputMode(DropInputMode.NONE);
- }
-
- /**
- * We don't need to drop input for fully trusted embedding (system app, and embedding in the
- * same app). This will allow users to do fast tapping.
- */
- @Test
- public void testOverrideTaskFragmentAdapter_noInputProtectedForFullyTrustedAnimation() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with only trusted embedded activity
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(1)
- .setOrganizer(organizer)
- .build();
- final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
- prepareActivityForAppTransition(activity);
- final int uid = mAtm.mTaskFragmentOrganizerController.getTaskFragmentOrganizerUid(
- getITaskFragmentOrganizer(organizer));
- doReturn(true).when(task).isFullyTrustedEmbedding(uid);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // The animation will be animated remotely by client, but input should not be dropped for
- // fully trusted.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- verify(activity, never()).setDropInputForAnimation(true);
- verify(activity, never()).setDropInputMode(DropInputMode.ALL);
- }
-
- @Test
- public void testTransitionGoodToGoForTaskFragments() {
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final Task task = createTask(mDisplayContent);
- final TaskFragment changeTaskFragment =
- createTaskFragmentWithEmbeddedActivity(task, organizer);
- final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(organizer)
- .build();
- prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity());
- spyOn(mDisplayContent.mAppTransition);
- spyOn(emptyTaskFragment);
-
- prepareAndTriggerAppTransition(
- null /* openingActivity */, null /* closingActivity*/, changeTaskFragment);
-
- // Transition not ready because there is an empty non-finishing TaskFragment.
- verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
-
- doReturn(true).when(emptyTaskFragment).hasChild();
- emptyTaskFragment.remove(false /* withTransition */, "test");
-
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
- // Transition ready because the empty (no running activity) TaskFragment is requested to be
- // removed.
- verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
- }
-
- @Test
- public void testTransitionGoodToGoForTaskFragments_detachedApp() {
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
- registerTaskFragmentOrganizer(iOrganizer);
- final Task task = createTask(mDisplayContent);
- final TaskFragment changeTaskFragment =
- createTaskFragmentWithEmbeddedActivity(task, organizer);
- final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(organizer)
- .build();
- prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity());
- // To make sure that having a detached activity won't cause any issue.
- final ActivityRecord detachedActivity = createActivityRecord(task);
- detachedActivity.removeImmediately();
- assertNull(detachedActivity.getRootTask());
- spyOn(mDisplayContent.mAppTransition);
- spyOn(emptyTaskFragment);
-
- prepareAndTriggerAppTransition(
- null /* openingActivity */, detachedActivity, changeTaskFragment);
-
- // Transition not ready because there is an empty non-finishing TaskFragment.
- verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
-
- doReturn(true).when(emptyTaskFragment).hasChild();
- emptyTaskFragment.remove(false /* withTransition */, "test");
-
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
- // Transition ready because the empty (no running activity) TaskFragment is requested to be
- // removed.
- verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
- }
-
- /** Registers remote animation for the organizer. */
- private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
- TestRemoteAnimationRunner remoteAnimationRunner) {
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- remoteAnimationRunner, 10, 1);
- final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
- registerTaskFragmentOrganizer(iOrganizer);
- mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
- }
-
- private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
- TaskFragmentOrganizer organizer) {
- return ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
- }
-
- private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
- @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) {
- if (openingActivity != null) {
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
- mDisplayContent.mOpeningApps.add(openingActivity);
- }
- if (closingActivity != null) {
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0);
- mDisplayContent.mClosingApps.add(closingActivity);
- }
- if (changingTaskFragment != null) {
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
- mDisplayContent.mChangingContainers.add(changingTaskFragment);
- }
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
- }
-
- private static void prepareActivityForAppTransition(ActivityRecord activity) {
- // Transition will wait until all participated activities to be drawn.
- activity.allDrawn = true;
- // Skip manipulate the SurfaceControl.
- doNothing().when(activity).setDropInputMode(anyInt());
- // Assume the activity contains a window.
- doReturn(true).when(activity).hasChild();
- // Make sure activity can create remote animation target.
- doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
- any());
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
deleted file mode 100644
index 8553fbd30ab8..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_UNSET;
-import static android.view.WindowManager.TRANSIT_OPEN;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.animation.Animation;
-import android.window.ITaskFragmentOrganizer;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.policy.TransitionAnimation;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link AppTransition}.
- *
- * Build/Install/Run:
- * atest WmTests:AppTransitionTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppTransitionTests extends WindowTestsBase {
- private DisplayContent mDc;
-
- @Before
- public void setUp() throws Exception {
- doNothing().when(mWm.mRoot).performSurfacePlacement();
- mDc = mWm.getDefaultDisplayContentLocked();
- }
-
- @Test
- public void testKeyguardOverride() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
- mDc.mOpeningApps.add(activity);
- assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testKeyguardUnoccludeOcclude() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
- mDc.mOpeningApps.add(activity);
- assertEquals(TRANSIT_NONE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-
- }
-
- @Test
- public void testKeyguardKeep() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.mOpeningApps.add(activity);
- assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testCrashing() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
- mDc.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testKeepKeyguard_withCrashing() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
- mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
- mDc.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testSkipTransitionAnimation() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CLOSE);
- mDc.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_UNSET,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, true /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testTaskChangeWindowingMode() {
- final ActivityRecord activity = createActivityRecord(mDc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CHANGE);
- mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
- mDc.mChangingContainers.add(activity.getTask());
-
- assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testTaskFragmentChange() {
- final ActivityRecord activity = createActivityRecord(mDc);
- final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(),
- true /* createdByOrganizer */, true /* isEmbedded */);
- activity.getTask().addChild(taskFragment, POSITION_TOP);
- activity.reparent(taskFragment, POSITION_TOP);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CHANGE);
- mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
- mDc.mChangingContainers.add(taskFragment);
-
- assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testTaskFragmentOpeningTransition() {
- final ActivityRecord activity = createHierarchyForTaskFragmentTest();
- activity.setVisible(false);
-
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(activity);
- assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
- }
-
- @Test
- public void testTaskFragmentClosingTransition() {
- final ActivityRecord activity = createHierarchyForTaskFragmentTest();
- activity.setVisible(true);
-
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
- }
-
- /**
- * Creates a {@link Task} with two {@link TaskFragment TaskFragments}.
- * The bottom TaskFragment is to prevent
- * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation
- * target} to promote to Task or above.
- *
- * @return The Activity to be put in either opening or closing Activity
- */
- private ActivityRecord createHierarchyForTaskFragmentTest() {
- final Task parentTask = createTask(mDisplayContent);
- final TaskFragment bottomTaskFragment = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity();
- bottomActivity.setOccludesParent(true);
- bottomActivity.setVisible(true);
-
- final TaskFragment verifiedTaskFragment = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity();
- activity.setOccludesParent(true);
-
- return activity;
- }
-
- @Test
- public void testAppTransitionStateForMultiDisplay() {
- // Create 2 displays & presume both display the state is ON for ready to display & animate.
- final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
- final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
-
- // Create 2 app window tokens to represent 2 activity window.
- final ActivityRecord activity1 = createActivityRecord(dc1);
- final ActivityRecord activity2 = createActivityRecord(dc2);
-
- activity1.allDrawn = true;
- activity1.startingMoved = true;
-
- // Simulate activity resume / finish flows to prepare app transition & set visibility,
- // make sure transition is set as expected for each display.
- dc1.prepareAppTransition(TRANSIT_OPEN);
- dc2.prepareAppTransition(TRANSIT_CLOSE);
- // One activity window is visible for resuming & the other activity window is invisible
- // for finishing in different display.
- activity1.setVisibility(true);
- activity2.setVisibility(false);
-
- // Make sure each display is in animating stage.
- assertTrue(dc1.mOpeningApps.size() > 0);
- assertTrue(dc2.mClosingApps.size() > 0);
- assertTrue(dc1.isAppTransitioning());
- assertTrue(dc2.isAppTransitioning());
- }
-
- @Test
- public void testCleanAppTransitionWhenRootTaskReparent() {
- // Create 2 displays & presume both display the state is ON for ready to display & animate.
- final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
- final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
-
- final Task rootTask1 = createTask(dc1);
- final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
- final ActivityRecord activity1 = createNonAttachedActivityRecord(dc1);
- task1.addChild(activity1, 0);
-
- // Simulate same app is during opening / closing transition set stage.
- dc1.mClosingApps.add(activity1);
- assertTrue(dc1.mClosingApps.size() > 0);
-
- dc1.prepareAppTransition(TRANSIT_OPEN);
- assertTrue(dc1.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
- assertTrue(dc1.mAppTransition.isTransitionSet());
-
- dc1.mOpeningApps.add(activity1);
- assertTrue(dc1.mOpeningApps.size() > 0);
-
- // Move root task to another display.
- rootTask1.reparent(dc2.getDefaultTaskDisplayArea(), true);
-
- // Verify if token are cleared from both pending transition list in former display.
- assertFalse(dc1.mOpeningApps.contains(activity1));
- assertFalse(dc1.mOpeningApps.contains(activity1));
- }
-
- @Test
- public void testLoadAnimationSafely() {
- DisplayContent dc = createNewDisplay(Display.STATE_ON);
- assertNull(dc.mAppTransition.loadAnimationSafely(
- getInstrumentation().getTargetContext(), -1));
- }
-
- @Test
- public void testCancelRemoteAnimationWhenFreeze() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- doReturn(false).when(dc).onDescendantOrientationChanged(any());
- final WindowState exitingAppWindow = newWindowBuilder("exiting app",
- TYPE_BASE_APPLICATION).setDisplay(dc).build();
- final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
- // Wait until everything in animation handler get executed to prevent the exiting window
- // from being removed during WindowSurfacePlacer Traversal.
- waitUntilHandlersIdle();
-
- // Set a remote animator.
- final TestRemoteAnimationRunner runner = new TestRemoteAnimationRunner();
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- runner, 100, 50, true /* changeNeedsSnapshot */);
- // RemoteAnimationController will tracking RemoteAnimationAdapter's caller with calling pid.
- adapter.setCallingPidUid(123, 456);
-
- // Simulate activity finish flows to prepare app transition & set visibility,
- // make sure transition is set as expected.
- dc.prepareAppTransition(TRANSIT_CLOSE);
- assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_CLOSE));
- dc.mAppTransition.overridePendingAppTransitionRemote(adapter);
- exitingActivity.setVisibility(false);
- assertTrue(dc.mClosingApps.size() > 0);
-
- // Make sure window is in animating stage before freeze, and cancel after freeze.
- assertTrue(dc.isAppTransitioning());
- assertFalse(runner.mCancelled);
- dc.mAppTransition.freeze();
- assertFalse(dc.isAppTransitioning());
- assertTrue(runner.mCancelled);
- }
-
- @Test
- public void testGetAnimationStyleResId() {
- // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
- // specifying window type.
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
- attrs.windowAnimations = 0x12345678;
- assertEquals(attrs.windowAnimations, mDc.mAppTransition.getAnimationStyleResId(attrs));
-
- // Verify getAnimationStyleResId will return system resource Id when the window type is
- // starting window.
- attrs.type = TYPE_APPLICATION_STARTING;
- assertEquals(mDc.mAppTransition.getDefaultWindowAnimationStyleResId(),
- mDc.mAppTransition.getAnimationStyleResId(attrs));
- }
-
- @Test
- public void testActivityRecordReparentedToTaskFragment() {
- final ActivityRecord activity = createActivityRecord(mDc);
- final SurfaceControl activityLeash = mock(SurfaceControl.class);
- doNothing().when(activity).setDropInputMode(anyInt());
- activity.setVisibility(true);
- activity.setSurfaceControl(activityLeash);
- final Task task = activity.getTask();
-
- // Add a TaskFragment of half of the Task size.
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final ITaskFragmentOrganizer iOrganizer =
- ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
- registerTaskFragmentOrganizer(iOrganizer);
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(organizer)
- .build();
- final Rect taskBounds = new Rect();
- task.getBounds(taskBounds);
- taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
- spyOn(taskFragment);
-
- assertTrue(mDc.mChangingContainers.isEmpty());
- assertFalse(mDc.mAppTransition.isTransitionSet());
-
- // Schedule app transition when reparent activity to a TaskFragment of different size.
- final Rect startBounds = new Rect(activity.getBounds());
- activity.reparent(taskFragment, POSITION_TOP);
-
- // It should transit at TaskFragment level with snapshot on the activity surface.
- verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
- assertTrue(mDc.mChangingContainers.contains(taskFragment));
- assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
- }
-
- @Test
- public void testGetNextAppTransitionBackgroundColor() {
- assumeFalse(WindowManagerService.sEnableShellTransitions);
-
- // No override by default.
- assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
-
- // Override with a custom color.
- mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
- final int testColor = 123;
- mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
- 0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
- false /* overrideTaskTransaction */);
-
- assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
- assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Override with ActivityEmbedding remote animation. Background color should be kept.
- mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
- false /* sync */, true /* isActivityEmbedding */);
-
- assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
- assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Background color should not be cleared anymore after #clear().
- mDc.mAppTransition.clear();
- assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
- assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
- }
-
- @Test
- public void testGetNextAppRequestedAnimation() {
- assumeFalse(WindowManagerService.sEnableShellTransitions);
- final String packageName = "testPackage";
- final int enterAnimResId = 1;
- final int exitAnimResId = 2;
- final int testColor = 123;
- final Animation enterAnim = mock(Animation.class);
- final Animation exitAnim = mock(Animation.class);
- final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
- spyOn(transitionAnimation);
- doReturn(enterAnim).when(transitionAnimation)
- .loadAppTransitionAnimation(packageName, enterAnimResId);
- doReturn(exitAnim).when(transitionAnimation)
- .loadAppTransitionAnimation(packageName, exitAnimResId);
-
- // No override by default.
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
-
- // Override with a custom animation.
- mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
- mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
- testColor, null /* startedCallback */, null /* endedCallback */,
- false /* overrideTaskTransaction */);
-
- assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
- assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Override with ActivityEmbedding remote animation. Custom animation should be kept.
- mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
- false /* sync */, true /* isActivityEmbedding */);
-
- assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
- assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Custom animation should not be cleared anymore after #clear().
- mDc.mAppTransition.clear();
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
- }
-
- private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
- boolean mCancelled = false;
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- }
-
- @Override
- public void onAnimationCancelled() {
- mCancelled = true;
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 0964ebed9d25..82435b24dad6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1172,11 +1172,12 @@ public class DisplayContentTests extends WindowTestsBase {
.setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
prev.setVisibleRequested(false);
final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false)
.setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
mDisplayContent.rotationForActivityInDifferentOrientation(top));
- mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ requestTransition(top, WindowManager.TRANSIT_OPEN);
top.setVisibility(true);
mDisplayContent.updateOrientation();
// The top uses "behind", so the orientation is decided by the previous.
@@ -1609,8 +1610,7 @@ public class DisplayContentTests extends WindowTestsBase {
final ActivityRecord app = mAppWindow.mActivityRecord;
app.setVisible(false);
app.setVisibleRequested(false);
- registerTestTransitionPlayer();
- mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ requestTransition(app, WindowManager.TRANSIT_OPEN);
app.setVisibility(true);
final int newOrientation = getRotatedOrientation(mDisplayContent);
app.setRequestedOrientation(newOrientation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 6c5fe1d8551e..71e34ef220d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -53,6 +53,7 @@ import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import androidx.test.filters.SmallTest;
@@ -400,9 +401,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertTrue(state.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars()));
assertTrue(state.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars()));
- mAppWindow.setRequestedVisibleTypes(
+ final @InsetsType int changedTypes = mAppWindow.setRequestedVisibleTypes(
navigationBars() | statusBars(), navigationBars() | statusBars());
- policy.onRequestedVisibleTypesChanged(mAppWindow, null /* statsToken */);
+ policy.onRequestedVisibleTypesChanged(mAppWindow, changedTypes, null /* statsToken */);
waitUntilWindowAnimatorIdle();
controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 973c8d0a8464..5525bae89138 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -52,6 +52,7 @@ import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.WindowInsets.Type.InsetsType;
import androidx.test.filters.SmallTest;
@@ -201,8 +202,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(base);
- base.setRequestedVisibleTypes(ime(), ime());
- getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+ final @InsetsType int changedTypes = base.setRequestedVisibleTypes(ime(), ime());
+ getController().onRequestedVisibleTypesChanged(base, changedTypes, null /* statsToken */);
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
@@ -509,8 +510,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.updateImeInputAndControlTarget(app);
- app.setRequestedVisibleTypes(ime(), ime());
- getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
+ final @InsetsType int changedTypes = app.setRequestedVisibleTypes(ime(), ime());
+ getController().onRequestedVisibleTypesChanged(app, changedTypes, null /* statsToken */);
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
if (android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
new file mode 100644
index 000000000000..db90c28ec7df
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.FLAG_PRESENTATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.graphics.Rect;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.IWindow;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:PresentationControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PresentationControllerTests extends WindowTestsBase {
+
+ @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testPresentationHidesActivitiesBehind() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.flags = FLAG_PRESENTATION;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ final int displayId = dc.getDisplayId();
+ doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ final ActivityRecord activity = createActivityRecord(createTask(dc));
+ assertTrue(activity.isVisible());
+
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int uid = 100000; // uid for non-system user
+ final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
+ final int userId = UserHandle.getUserId(uid);
+ doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_PRESENTATION);
+
+ final IWindow clientWindow = new TestIWindow();
+ final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+ userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
+ new InsetsSourceControl.Array(), new Rect(), new float[1]);
+ assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
+ assertFalse(activity.isVisible());
+
+ final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
+ window.removeImmediately();
+ assertTrue(activity.isVisible());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index fc4f54a431d6..e4a1bf603cf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -454,31 +454,6 @@ public class RootWindowContainerTests extends WindowTestsBase {
}
@Test
- public void testMovingBottomMostRootTaskActivityToPinnedRootTask() {
- final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
- final Task task = firstActivity.getTask();
-
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
-
- fullscreenTask.moveTaskToBack(task);
-
- // Ensure full screen task has both tasks.
- ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
- assertEquals(task.getTopMostActivity(), secondActivity);
- firstActivity.setState(STOPPED, "testMovingBottomMostRootTaskActivityToPinnedRootTask");
-
-
- // Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
-
- assertTrue(firstActivity.mRequestForceTransition);
- }
-
- @Test
public void testMultipleActivitiesTaskEnterPip() {
// Enable shell transition because the order of setting windowing mode is different.
registerTestTransitionPlayer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index dba463a436c0..95bca2b17efb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1811,9 +1811,9 @@ public class SizeCompatTests extends WindowTestsBase {
}
addStatusBar(mActivity.mDisplayContent);
- mActivity.setVisible(false);
- mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
- mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+ mActivity.setVisibleRequested(false);
+ requestTransition(mActivity, WindowManager.TRANSIT_OPEN);
+ mActivity.setVisibility(true);
final float maxAspect = 1.8f;
prepareUnresizable(mActivity, maxAspect, SCREEN_ORIENTATION_LANDSCAPE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 7dba1422d61d..2544550120d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -22,6 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -69,7 +70,6 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
private MyAnimatable mAnimatable;
private MyAnimatable mAnimatable2;
- private DeferFinishAnimatable mDeferFinishAnimatable;
@Before
public void setUp() throws Exception {
@@ -77,14 +77,12 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
mAnimatable = new MyAnimatable(mWm, mTransaction);
mAnimatable2 = new MyAnimatable(mWm, mTransaction);
- mDeferFinishAnimatable = new DeferFinishAnimatable(mWm, mTransaction);
}
@After
public void tearDown() {
mAnimatable = null;
mAnimatable2 = null;
- mDeferFinishAnimatable = null;
}
@Test
@@ -202,41 +200,33 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
}
@Test
- public void testDeferFinish() {
-
- // Start animation
- final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
-
- // Finish the animation but then make sure we are deferring.
- onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
- assertAnimating(mDeferFinishAnimatable);
-
- // Now end defer finishing.
- mDeferFinishAnimatable.mEndDeferFinishCallback.run();
- assertNotAnimating(mAnimatable2);
- assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_APP_TRANSITION, mDeferFinishAnimatable.mFinishedAnimationType);
- verify(mTransaction).remove(eq(mDeferFinishAnimatable.mLeash));
- }
-
- @Test
public void testDeferFinishDoNotFinishNextAnimation() {
+ final DeferredFinishAdapter deferredFinishAdapter = new DeferredFinishAdapter();
+ spyOn(deferredFinishAdapter);
// Start the first animation.
- final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
- onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
+ mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, deferredFinishAdapter,
+ true /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+ assertAnimating(mAnimatable);
+ final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+ OnAnimationFinishedCallback.class);
+ verify(deferredFinishAdapter).startAnimation(any(), any(),
+ eq(ANIMATION_TYPE_WINDOW_ANIMATION), callbackCaptor.capture());
+ final OnAnimationFinishedCallback onFinishedCallback = callbackCaptor.getValue();
+ onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+ deferredFinishAdapter);
// The callback is the resetAndInvokeFinish in {@link SurfaceAnimator#getFinishedCallback}.
- final Runnable firstDeferFinishCallback = mDeferFinishAnimatable.mEndDeferFinishCallback;
+ final Runnable firstDeferFinishCallback = deferredFinishAdapter.mEndDeferFinishCallback;
// Start the second animation.
- mDeferFinishAnimatable.mSurfaceAnimator.cancelAnimation();
- startDeferFinishAnimatable(mSpec2);
- mDeferFinishAnimatable.mFinishedCallbackCalled = false;
+ mAnimatable.mSurfaceAnimator.cancelAnimation();
+ mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2,
+ true /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+ mAnimatable.mFinishedCallbackCalled = false;
- // Simulate the first deferred callback is executed from
- // {@link AnimatingActivityRegistry#endDeferringFinished}.
+ // Simulate the first deferred callback is executed.
firstDeferFinishCallback.run();
// The second animation should not be finished.
- assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled);
+ assertFalse(mAnimatable.mFinishedCallbackCalled);
}
@Test
@@ -260,17 +250,6 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
verify(mTransaction).remove(eq(deferredFinishAdapter.mAnimationLeash));
}
- private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) {
- mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim,
- true /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
- OnAnimationFinishedCallback.class);
- assertAnimating(mDeferFinishAnimatable);
- verify(anim).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
- callbackCaptor.capture());
- return callbackCaptor.getValue();
- }
-
private void assertAnimating(MyAnimatable animatable) {
assertTrue(animatable.mSurfaceAnimator.isAnimating());
assertNotNull(animatable.mSurfaceAnimator.getAnimation());
@@ -370,21 +349,6 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
};
}
- private static class DeferFinishAnimatable extends MyAnimatable {
-
- Runnable mEndDeferFinishCallback;
-
- DeferFinishAnimatable(WindowManagerService wm, Transaction transaction) {
- super(wm, transaction);
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- mEndDeferFinishCallback = endDeferFinishCallback;
- return true;
- }
- }
-
private static class DeferredFinishAdapter implements AnimationAdapter {
private Runnable mEndDeferFinishCallback;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 001446550304..edffab801499 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -32,8 +32,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -85,12 +83,8 @@ import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowInsets;
@@ -1055,25 +1049,6 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Test
- public void testTaskCanApplyAnimation() {
- final Task rootTask = createTask(mDisplayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
- verifyWindowContainerApplyAnimation(task, activity1, activity2);
- }
-
- @Test
- public void testRootTaskCanApplyAnimation() {
- final Task rootTask = createTask(mDisplayContent);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- createTaskInRootTask(rootTask, 0 /* userId */));
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
- createTaskInRootTask(rootTask, 0 /* userId */));
- verifyWindowContainerApplyAnimation(rootTask, activity1, activity2);
- }
-
- @Test
public void testGetDisplayArea() {
// WindowContainer
final WindowContainer windowContainer = new WindowContainer(mWm);
@@ -1103,59 +1078,6 @@ public class WindowContainerTests extends WindowTestsBase {
assertEquals(displayArea, displayArea.getDisplayArea());
}
- private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act,
- ActivityRecord act2) {
- // Initial remote animation for app transition.
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
- }, 0, 0, false);
- adapter.setCallingPidUid(123, 456);
- wc.getDisplayContent().prepareAppTransition(TRANSIT_OPEN);
- wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
- spyOn(wc);
- doReturn(true).when(wc).okToAnimate();
-
- // Make sure animating state is as expected after applied animation.
-
- // Animation target is promoted from act to wc. act2 is a descendant of wc, but not a source
- // of the animation.
- ArrayList<WindowContainer<WindowState>> sources = new ArrayList<>();
- sources.add(act);
- assertTrue(wc.applyAnimation(null, TRANSIT_OLD_TASK_OPEN, true, false, sources));
-
- assertEquals(act, wc.getTopMostActivity());
- assertTrue(wc.isAnimating());
- assertTrue(wc.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
- assertTrue(wc.getAnimationSources().contains(act));
- assertFalse(wc.getAnimationSources().contains(act2));
- assertTrue(act.isAnimating(PARENTS));
- assertTrue(act.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
- assertEquals(wc, act.getAnimatingContainer(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
-
- // Make sure animation finish callback will be received and reset animating state after
- // animation finish.
- wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_OLD_TASK_OPEN, act);
- verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any());
- assertFalse(wc.isAnimating());
- assertFalse(act.isAnimating(PARENTS));
- }
-
@Test
public void testRegisterWindowContainerListener() {
final WindowContainer container = new WindowContainer(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 1323d8a59cef..71e84c0f1821 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,7 +26,6 @@ import static android.permission.flags.Flags.FLAG_SENSITIVE_CONTENT_RECENTS_SCRE
import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
-import static android.view.Display.FLAG_PRESENTATION;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -55,7 +54,6 @@ import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -102,7 +100,6 @@ import android.provider.Settings;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
-import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -1409,38 +1406,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
assertEquals(activityWindowInfo2, activityWindowInfo3);
}
- @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
- @Test
- public void testPresentationHidesActivitiesBehind() {
- DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.copyFrom(mDisplayInfo);
- displayInfo.flags = FLAG_PRESENTATION;
- DisplayContent dc = createNewDisplay(displayInfo);
- int displayId = dc.getDisplayId();
- doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
- ActivityRecord activity = createActivityRecord(createTask(dc));
- assertTrue(activity.isVisible());
-
- doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
- int uid = 100000; // uid for non-system user
- Session session = createTestSession(mAtm, 1234 /* pid */, uid);
- int userId = UserHandle.getUserId(uid);
- doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- LayoutParams.TYPE_PRESENTATION);
-
- final IWindow clientWindow = new TestIWindow();
- int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
- userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
- new InsetsSourceControl.Array(), new Rect(), new float[1]);
- assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
- assertFalse(activity.isVisible());
-
- final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
- window.removeImmediately();
- assertTrue(activity.isVisible());
- }
-
@Test
public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1281be5132d3..7030d986494f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -26,6 +26,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -33,6 +34,8 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
@@ -1983,6 +1986,30 @@ public class WindowOrganizerTests extends WindowTestsBase {
testSetAlwaysOnTop(displayArea);
}
+ @Test
+ public void testConfigurationsAreEqualForOrganizer() {
+ Configuration config1 = new Configuration();
+ config1.smallestScreenWidthDp = 300;
+ config1.uiMode = UI_MODE_NIGHT_YES;
+
+ Configuration config2 = new Configuration(config1);
+ config2.uiMode = UI_MODE_NIGHT_NO;
+
+ Configuration config3 = new Configuration(config1);
+ config3.smallestScreenWidthDp = 500;
+
+ // Should be equal for non-controllable configuration changes.
+ assertTrue(WindowOrganizerController.configurationsAreEqualForOrganizer(config1, config2));
+
+ // Should be unequal for non-controllable configuration changes if the organizer is
+ // interested in that change.
+ assertFalse(WindowOrganizerController.configurationsAreEqualForOrganizer(
+ config1, config2, CONFIG_UI_MODE));
+
+ // Should be unequal for controllable configuration changes.
+ assertFalse(WindowOrganizerController.configurationsAreEqualForOrganizer(config1, config3));
+ }
+
private void testSetAlwaysOnTop(WindowContainer wc) {
final WindowContainerTransaction t = new WindowContainerTransaction();
t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index b16f5283d532..7f9e591ca5e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -961,6 +961,15 @@ public class WindowTestsBase extends SystemServiceTestsBase {
return testPlayer;
}
+ void requestTransition(WindowContainer<?> wc, int transit) {
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ if (controller.getTransitionPlayer() == null) {
+ registerTestTransitionPlayer();
+ }
+ controller.requestTransitionIfNeeded(transit, 0 /* flags */, null /* trigger */,
+ wc.mDisplayContent);
+ }
+
/** Overrides the behavior of config_reverseDefaultRotation for the given display. */
void setReverseDefaultRotation(DisplayContent dc, boolean reverse) {
final DisplayRotation displayRotation = dc.getDisplayRotation();
@@ -1417,7 +1426,9 @@ public class WindowTestsBase extends SystemServiceTestsBase {
activity.setProcess(wpc);
// Resume top activities to make sure all other signals in the system are connected.
- mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+ if (mVisible) {
+ mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
return activity;
}
}
diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml
index 37321ad80b0f..758852bb1074 100644
--- a/tests/AttestationVerificationTest/AndroidManifest.xml
+++ b/tests/AttestationVerificationTest/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.security.attestationverification">
- <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
<application>
diff --git a/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json b/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json
new file mode 100644
index 000000000000..2a3ba5ebde7d
--- /dev/null
+++ b/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json
@@ -0,0 +1,12 @@
+{
+ "entries": {
+ "6681152659205225093" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "8350192447815228107" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json b/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json
new file mode 100644
index 000000000000..e22a834a92bf
--- /dev/null
+++ b/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json
@@ -0,0 +1,16 @@
+{
+ "entries": {
+ "6681152659205225093" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "353017e73dc205a73a9c3de142230370" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "8350192447815228107" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
new file mode 100644
index 000000000000..c38517ace5e6
--- /dev/null
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2025 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.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class CertificateRevocationStatusManagerTest {
+
+ private static final String TEST_CERTIFICATE_FILE_1 = "test_attestation_with_root_certs.pem";
+ private static final String TEST_CERTIFICATE_FILE_2 = "test_attestation_wrong_root_certs.pem";
+ private static final String TEST_REVOCATION_LIST_FILE_NAME = "test_revocation_list.json";
+ private static final String REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST =
+ "test_revocation_list_no_test_certs.json";
+ private static final String REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST =
+ "test_revocation_list_with_test_certs.json";
+ private static final String TEST_REVOCATION_STATUS_FILE_NAME = "test_revocation_status.txt";
+ private static final String FILE_URL_PREFIX = "file://";
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ private CertificateFactory mFactory;
+ private List<X509Certificate> mCertificates1;
+ private List<X509Certificate> mCertificates2;
+ private File mRevocationListFile;
+ private String mRevocationListUrl;
+ private String mNonExistentRevocationListUrl;
+ private File mRevocationStatusFile;
+ private CertificateRevocationStatusManager mCertificateRevocationStatusManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mFactory = CertificateFactory.getInstance("X.509");
+ mCertificates1 = getCertificateChain(TEST_CERTIFICATE_FILE_1);
+ mCertificates2 = getCertificateChain(TEST_CERTIFICATE_FILE_2);
+ mRevocationListFile = new File(mContext.getFilesDir(), TEST_REVOCATION_LIST_FILE_NAME);
+ mRevocationListUrl = FILE_URL_PREFIX + mRevocationListFile.getAbsolutePath();
+ File noSuchFile = new File(mContext.getFilesDir(), "file_does_not_exist");
+ mNonExistentRevocationListUrl = FILE_URL_PREFIX + noSuchFile.getAbsolutePath();
+ mRevocationStatusFile = new File(mContext.getFilesDir(), TEST_REVOCATION_STATUS_FILE_NAME);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mRevocationListFile.delete();
+ mRevocationStatusFile.delete();
+ }
+
+ @Test
+ public void checkRevocationStatus_doesNotExistOnRemoteRevocationList_noException()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void checkRevocationStatus_existsOnRemoteRevocationList_throwsException()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void
+ checkRevocationStatus_cannotReachRemoteRevocationList_noStoredStatus_throwsException()
+ throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void checkRevocationStatus_savesRevocationStatus() throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+
+ assertThat(mRevocationStatusFile.length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_certsSaved_noException()
+ throws Exception {
+ // call checkRevocationStatus once to save the revocation status
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // call checkRevocationStatus again with mNonExistentRevocationListUrl
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_someCertsNotSaved_exception()
+ throws Exception {
+ // call checkRevocationStatus once to save the revocation status for mCertificates2
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates2);
+ // call checkRevocationStatus again with mNonExistentRevocationListUrl, this time for
+ // mCertificates1
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_someCertsStatusTooOld_exception()
+ throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+ LocalDateTime now = LocalDateTime.now();
+ LocalDateTime expiredStatusDate =
+ now.minusDays(CertificateRevocationStatusManager.MAX_DAYS_SINCE_LAST_CHECK + 1);
+ Map<String, LocalDateTime> lastRevocationCheckData = new HashMap<>();
+ lastRevocationCheckData.put(getSerialNumber(mCertificates1.get(0)), expiredStatusDate);
+ for (int i = 1; i < mCertificates1.size(); i++) {
+ lastRevocationCheckData.put(getSerialNumber(mCertificates1.get(i)), now);
+ }
+ mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastRevocationCheckData);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_allCertResultsFresh_noException()
+ throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+ LocalDateTime bearlyNotExpiredStatusDate =
+ LocalDateTime.now()
+ .minusDays(
+ CertificateRevocationStatusManager.MAX_DAYS_SINCE_LAST_CHECK - 1);
+ Map<String, LocalDateTime> lastRevocationCheckData = new HashMap<>();
+ for (X509Certificate certificate : mCertificates1) {
+ lastRevocationCheckData.put(getSerialNumber(certificate), bearlyNotExpiredStatusDate);
+ }
+ mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastRevocationCheckData);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void updateLastRevocationCheckData_correctlySavesStatus() throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+ Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+ for (X509Certificate certificate : mCertificates1) {
+ areCertificatesRevoked.put(getSerialNumber(certificate), false);
+ }
+
+ mCertificateRevocationStatusManager.updateLastRevocationCheckData(areCertificatesRevoked);
+
+ // no exception
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // revoke one certificate and try again
+ areCertificatesRevoked.put(getSerialNumber(mCertificates1.getLast()), true);
+ mCertificateRevocationStatusManager.updateLastRevocationCheckData(areCertificatesRevoked);
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void updateLastRevocationCheckDataForAllPreviouslySeenCertificates_updatesCorrectly()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ // populate the revocation status file
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // Sleep for 2 second so that the current time changes
+ SystemClock.sleep(2000);
+ LocalDateTime timestampBeforeUpdate = LocalDateTime.now();
+ JSONObject revocationList = mCertificateRevocationStatusManager.fetchRemoteRevocationList();
+ List<String> otherCertificatesToCheck = new ArrayList<>();
+ String serialNumber1 = "1234567"; // not revoked
+ String serialNumber2 = "8350192447815228107"; // revoked
+ String serialNumber3 = "987654"; // not revoked
+ otherCertificatesToCheck.add(serialNumber1);
+ otherCertificatesToCheck.add(serialNumber2);
+ otherCertificatesToCheck.add(serialNumber3);
+
+ mCertificateRevocationStatusManager
+ .updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+ revocationList, otherCertificatesToCheck);
+
+ Map<String, LocalDateTime> lastRevocationCheckData =
+ mCertificateRevocationStatusManager.getLastRevocationCheckData();
+ assertThat(lastRevocationCheckData.get(serialNumber1)).isAtLeast(timestampBeforeUpdate);
+ assertThat(lastRevocationCheckData).doesNotContainKey(serialNumber2); // revoked
+ assertThat(lastRevocationCheckData.get(serialNumber3)).isAtLeast(timestampBeforeUpdate);
+ // validate that the existing certificates on the file got updated too
+ for (X509Certificate certificate : mCertificates1) {
+ assertThat(lastRevocationCheckData.get(getSerialNumber(certificate)))
+ .isAtLeast(timestampBeforeUpdate);
+ }
+ }
+
+ private List<X509Certificate> getCertificateChain(String fileName) throws Exception {
+ Collection<? extends Certificate> certificates =
+ mFactory.generateCertificates(mContext.getResources().getAssets().open(fileName));
+ ArrayList<X509Certificate> x509Certs = new ArrayList<>();
+ for (Certificate cert : certificates) {
+ x509Certs.add((X509Certificate) cert);
+ }
+ return x509Certs;
+ }
+
+ private void copyFromAssetToFile(String assetFileName, File targetFile) throws Exception {
+ byte[] data;
+ try (InputStream in = mContext.getResources().getAssets().open(assetFileName)) {
+ data = in.readAllBytes();
+ }
+ try (FileOutputStream fileOutputStream = new FileOutputStream(targetFile)) {
+ fileOutputStream.write(data);
+ }
+ }
+
+ private String getSerialNumber(X509Certificate certificate) {
+ return certificate.getSerialNumber().toString(16);
+ }
+}
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 12670cda74b2..ac704e5e7c39 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -52,10 +52,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 481a8bb66fee..1b2007deae27 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -50,10 +50,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>